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

时间:2011-03-14 21:34:20

标签: iphone objective-c cocoa

我正在寻找从double或short实例化NSDecimalNumber的最佳方法。有以下NSNumber类和实例方法......

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

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

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

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

希望以最干净,最正确的方式投入......

5 个答案:

答案 0 :(得分:70)

你走在正确的轨道上(有警告,见下文)。你应该能够使用NSNumber中的初始化者,因为它们是由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]);

有关此主题的更多信息,请访问here

但是,我在Stack Overflow和网络上看到了很多关于初始化NSDecimalNumber问题的讨论。似乎存在与精确度和与NSDecimalNumber的双打转换相关的一些问题。特别是当您使用从NSNumber继承的初始化成员时。

我敲了下面的测试:

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

因此,您可以看到numberWithDouble上的NSNumber便捷方法(由于它返回错误的指针类型而不应该使用),甚至是初始化程序initWithDouble (IMO“应该”可以调用)你得到一个NSDecimalNumber,它带有一个内部表示(通过在对象上调用description返回),它不像你调用{{1}时那样精确}或decimalNumberWithString:

另外,请注意,通过调用decimalNumberWithMantissa:exponent:isNegative:实例上的doubleValue来转换回双精度正在失去精确度,但无论您调用什么初始化器,都有趣,无论如何。

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

那么当你有双重,浮动或其他NSNumber时,如何轻松地调用这些初始化器?

两种方法描述here“工作”,但仍有精确问题。

如果您已将号码存储为NSNumber,则此工作:

NSDecimalNumber

但正如您从下面的输出中所看到的,它会消除一些不太重要的数字:

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

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

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到NSDecimalNumber的最一致准确的转换/初始化正在使用float。但是,正如Brad Larson在他的回答中指出的那样,这可能有点慢。如果性能成为一个问题,他使用NSScanner进行转换的技术可能会更好。

答案 1 :(得分:15)

如果在创建NSDecimalNumber时使用NSNumber的初始值设定项,则可能会出现一些问题。请参阅this question中发布的示例,了解其中的一个很好的例子。另外,我相信比尔·布姆加纳(Bill Bumgarner)对his response in the cocoa-dev mailing list的评论。另见Ashley的回答here

因为我对这些转换问题很偏执,所以我过去使用了以下代码来创建NSDecimal结构:

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

这里复杂代码的原因是我found NSScanner to be about 90% faster比从字符串初始化NSDecimalNumber。我使用NSDecimal结构,因为它们可以通过NSDecimalNumber兄弟yield significant performance advantages

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

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

但是,如果您正在使用NSDecimalNumbers,那么您将希望尽可能避免在任何时候将值转换为浮点数。从文本字段读回字符串值并将它们直接转换为NSDecimalNumbers,使用NSDecimalNumbers进行数学运算,并将它们作为NSStrings或Core Data中的十进制值写入磁盘。一旦在任何点将值转换为浮点数,就会开始引入浮点表示错误。

答案 2 :(得分:1)

你可以尝试:

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

显然有faster ways of doing this,但如果代码不是瓶颈,则可能不值得。

答案 3 :(得分:0)

-[NSNumber initWithFloat:]-[NSNumber initWithDouble:]实际上不返回NSNumber *个对象,它们返回id,而NSDecimalNumber继承自NSNumber,所以你可以使用:

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

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

答案 4 :(得分:-1)

或者您可以定义一个宏来缩短代码

#define DECIMAL(x) [[NSDecimalNumber alloc]initWithDouble:x]

budget.amount = DECIMAL(5000.00);