2011-03-14 70 views
36

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

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

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

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

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

想知道关于去清洁和正确的方式输入...

+0

记住它是类方法'alloc'用于确定对象不是在init *方法的类型。 initWithFloat不返回一个NSNumber。你在alloc返回的NSDecimalNumber实例上调用initWithFloat。 – orj 2011-03-14 22:00:07

+0

@orj - 是的,但是当我评论你的答案时,这有效地将NSNumber作为NSDecimalNumber进行类型化,这是不推荐的:http://www.cocoabuilder.com/archive/cocoa/227178-question-on-estimating -arraywithcapacity.html#227253。我在下面的评论中指出了一个失败的案例。 – 2011-03-14 23:32:26

回答

65

你是在正确的轨道上(有注意事项,见下文)。你应该只能使用来自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

但是,我已经看到了关于堆栈溢出和围绕Web初始化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(通过在obj上调用description返回ect)不如您在调用decimalNumberWithString:decimalNumberWithMantissa:exponent:isNegative:时得到的结果准确。

另外,请注意,通过调用NSDecimalNumber实例上的doubleValue转换回双精度将失去精度,但有趣的是,无论您调用哪个初始化程序,都会相同。

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

那么当你有双精度,浮点数或其他NSNumber时,你如何轻松地调用这些初始化函数呢?

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

如果您已经保存为一个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); 

但你可以从输出中看到它下面的LOP掉一些不太显著数字:

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:。但是,正如布拉德拉森在他的回答中指出的那样,这可能会有点慢。如果性能成为问题,他使用NSScanner进行转换的技术可能会更好。

+0

请注意,不建议使用NSNumber的初始值设定项和NSDecimalNumber。它可能会导致浮点数转换错误,如以下问题的示例所示:http://stackoverflow.com/questions/2479100/cocoa-ive-discovered-what-i-think-is-a-bug-带-nsdecimalnumber。另见Marcus Zarra的评论,他的文章在这里:http://www.cimgf.com/2008/04/23/cocoa-tutorial-dont-be-lazy-with-nsdecimalnumber-like-me/。这在cocoa-dev邮件列表中的某个时刻也提出了,但我现在找不到这个参考。 – 2011-03-14 23:09:38

+0

啊,这是Bill Bumgarner在cocoa-dev邮件列表上的回应:http://www.cocoabuilder.com/archive/cocoa/227178-question-on-estimating-arraywithcapacity.html#227253。 – 2011-03-14 23:13:42

+1

很好的后续分析。对于各种初始化程序会发生什么情况,很高兴看到一些结果。 – 2011-03-15 14:52:55

1

你可以尝试:

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

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

0

-[NSNumber initWithFloat:]-[NSNumber initWithDouble:]居然不返回NSNumber *对象,他们返回id,并从NSNumberNSDecimalNumber继承,所以你可以使用:

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

而且咄,我才意识到,同样也适用于+numberWithFloat:,所以你可以用它来代替。

13

如果您在创建NSDecimalNumber时使用NSNumber的初始值设定项,可能会出现一些问题。请参阅this question中发布的示例,了解这方面的一个很好的例子。另外,我相信Bill Bumgarner在his response in the cocoa-dev mailing list这个词上。另见阿什利的回答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或核心数据中的十进制值写入磁盘。只要您的值在任何点处转换为浮点数,就会开始引入浮点表示错误。

+0

布拉德,感谢您的分享。我如何去创建NSDecimal C风格的结构?我已经研究过它们,并没有太多的苹果文档 – Paul 2011-03-14 23:48:59

+1

@Paul - 我在我的答案[这里]描述了NSDecimal结构的内部格式(http://stackoverflow.com/questions/2035421/creating-nsdecimal/2035822 #2035822),它是通过检查相应的头文件获得的。但是,这种格式被认为是私人的,并且在头部之外没有记录。一般来说,创建它们的最安全的方法是通过从NSDecimalNumber中获取值或像上面那样在字符串上使用NSScanner。一旦你有了NSDecimal格式的值,通常很容易处理。 – 2011-03-15 04:38:24

-1

,或者可以是可以定义一个宏缩短代码

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

budget.amount = DECIMAL(5000.00); 
+0

这不提供问题的答案。一旦你有足够的[声誉](https://stackoverflow.com/help/whats-reputation),你将可以[对任何帖子发表评论](https://stackoverflow.com/help/privileges/comment);相反,[提供不需要提问者澄清的答案](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-c​​an- I-DO-代替)。 - [来自评论](/ review/low-quality-posts/17929434) – Sven 2017-11-13 16:50:40