从float或double实例化NSDecimalNumber的正确方法

我正在寻找从双或短实例化NSDecimalNumber的最佳方法。 有以下NSNumber类和实例方法…

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

但是这些似乎返回NSNumber。 另一方面,NSDecimalNumber定义了以下内容:

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

这里有几种可能性。 如果将NSDecimalNumber设置为上面的NSNumber便利方法的返回值,则Xcode会生成警告。

将感激最清洁和正确的方式去input…

你在正确的轨道上(有警告,见下文)。 你应该能够使用NSNumber中的初始值,因为它们是由NSDecimalNumberinheritance的。

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

关于这个问题的更多信息可以在这里find。

不过,我已经看到了关于堆栈溢出和围绕Web初始化NSDecimalNumber问题的很多讨论。 似乎有一些问题与NSDecimalNumber双精度和转换。 特别是当你使用从NSNumberinheritance的初始化成员。

我敲了下面的testing:

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

输出是:

 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 

所以你可以看到,当在NSNumber上使用numberWithDouble方便的方法(因为它返回错误的指针types,你不应该真的使用),甚至初始化程序initWithDouble (IMO“应该”可以调用),你会得到一个NSDecimalNumber有一个内部表示(通过调用对象的description返回),这不像调用decimalNumberWithString:时得到的那样准确decimalNumberWithString:或者decimalNumberWithMantissa:exponent:isNegative:

另外,请注意,通过在NSDecimalNumber实例上调用doubleValue来转换回double,正在失去精度,但有趣的是,无论您调用哪个初始化程序,都是一样的。

所以最后,我认为build议您在处理高精度浮点数时使用在NSDecimalNumber类级声明的decimalNumberWith*方法之一来创buildNSDecimalNumber实例。

那么当你有一个double,float或者其他的NSNumber的时候,你怎么轻松地调用这些初始化函数呢?

这里描述的两种方法“工作”,但仍然有精度问题。

如果你已经有一个NSNumber存储号码,那么这应该工作:

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

但是正如你从下面的输出中可以看到的那样,它忽略了一些不太重要的数字:

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

如果这个数字是一个原始的(float或者double)

 id dn2 = [NSDecimalNumber decimalNumberWithMantissa:(dbl * pow(10, 17)) exponent:-17 isNegative:(dbl < 0 ? YES : NO)]; NSLog(@"dn2 doubleValue: %.20f, description: %@", [dn2 doubleValue], dn2); 

但是你会再次得到精确的错误。 正如你在下面的输出中看到的那样:

 dn2 doubleValue: 36.76662445068357953915, description: 36.76662445068359168 

我认为这个精度损失的原因是由于涉及浮点乘法,因为下面的代码工作正常:

 id dn3 = [NSDecimalNumber decimalNumberWithMantissa:3676662445068359375L exponent:-17 isNegative:NO]; NSLog(@"dn3 doubleValue: %.20f, description: %@", [dn3 doubleValue], dn3); 

输出:

 dn3 doubleValue: 36.76662445068357953915, description: 36.76662445068359375 

因此,从doublefloat到NSDecimalNumber的最一致准确的转换/初始化是使用decimalNumberWithString: 但是,正如布拉德·拉森(Brad Larson)在答复中指出的那样,这可能有点慢。 如果性能成为问题,他使用NSScanner进行转换的技术可能会更好。

如果您在创buildNSDecimalNumber时使用NSNumber的初始值设定项,会出现一些问题。 看到这个问题中发布的例子是一个很好的例子。 另外,我信任Bill Bumgarner 在cocoa-dev邮件列表中的回应 。 另请参阅阿什利的答案。

因为我对这些转换问题有偏见,所以我在过去使用了下面的代码来创buildNSDecimal结构:

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

这里复杂的代码的原因是我发现NSScanner比从一个string初始化NSDecimalNumber 快大约90% 。 我使用NSDecimal结构,因为它们可以比NSDecimalNumber兄弟产生显着的性能优势 。

对于更简单但速度稍慢的NSDecimalNumber方法,您可以使用类似的东西

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

但是,如果你正在使用NSDecimalNumbers,那么你会想尽一切办法来避免在任何时候将值转换为浮点数。 从你的文本字段中读取string值,并将它们直接转换为NSDecimalNumbers,用NSDecimalNumbers进行math运算,然后将它们作为NSStrings或Core Data中的十进制值写入磁盘。 只要您的值在任何时刻转换为浮点数,就开始引入浮点表示错误。

你可以尝试:

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

显然,这样做有更快的方法 ,但如果代码不是瓶颈,则可能不值得。

-[NSNumber initWithFloat:]-[NSNumber initWithDouble:]实际上不返回NSNumber *对象,它们返回idNSDecimalNumberinheritance自NSNumber ,所以你可以使用:

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

而且,我刚刚意识到+numberWithFloat:也是如此+numberWithFloat:所以你可以直接使用它。

或者你也可以定义一个macros来缩短代码

 #define DECIMAL(x) [[NSDecimalNumber alloc]initWithDouble:x] budget.amount = DECIMAL(5000.00);