从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
因此,从double
或float
到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 *
对象,它们返回id
, NSDecimalNumber
inheritance自NSNumber
,所以你可以使用:
NSDecimalNumber *decimal = [[[NSDecimalNumber alloc] initWithFloat:1.1618] autorelease];
而且,我刚刚意识到+numberWithFloat:
也是如此+numberWithFloat:
所以你可以直接使用它。
或者你也可以定义一个macros来缩短代码
#define DECIMAL(x) [[NSDecimalNumber alloc]initWithDouble:x] budget.amount = DECIMAL(5000.00);