2011-03-14 67 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