在iOS開發中,和貨幣價格計算相關的,需要注意計算精度的問題。即使只是兩位小數,也會出現誤差。使用float類型運算,是完全不夠的。經過一番測試,最后選擇使用系統提供的API的NSDecimalNumber來進行更好的解決。
作為一個對外的庫,鑒於版本延續,我們保留對外的flaot的類型,不改變接口,選擇進行內部適配。
以下是一些基本的測試,
原始數據
float a = 0.01; int b = 99999999; double c = 0.0; |
1:使用浮點運算,
c = a*b; NSLog(@"%f",c); NSLog(@"%.2f",c); |
使用double類型存儲沒有觸及問題的實質,完全不能解決。
2011-12-30 11:04:00.121 Untitled[2912:207] 1000000.000000 2011-12-30 11:04:00.123 Untitled[2912:207] 1000000.00 |
2:使用類型轉換,提高精度
c = a*(double)b; NSLog(@"%f",c); NSLog(@"%.2f",c); |
Double運算的精度是提高了,可是浮點數的數值早已經出現了精度的不准確,即使存儲空間足夠,同樣還是不准確的數值。
2011-12-30 11:04:00.123 Untitled[2912:207] 999999.967648 2011-12-30 11:04:00.124 Untitled[2912:207] 999999.97 |
3:通過和NSString的轉換,將計算的原始數據轉換為純粹的double類型的數據,這樣的計算精度就可以達到要求了。
NSString *objA = [NSString stringWithFormat:@"%.2f", a]; NSString *objB = [NSString stringWithFormat:@"%.2f", (double)b]; c = [objA doubleValue] * [objB doubleValue]; NSLog(@"%.2f",c); |
計算的結果還是比較准確的,不過需要做格式化輸入和格式化輸出的處理。同時使用NSString來轉換,這樣的寫法看起來比較奇怪。
2011-12-30 11:04:00.190 Untitled[2912:207] 999999.99 |
4:個人還是比較喜歡使用系統提供的類型來進行計算。通過NSDecimalNumber提供的計算方式,可以很好的計算出准確的精度的數據,同時不需要使用格式化輸出等。
其計算的精度是比較高,這是官方建議的貨幣計算的API,對乘除等計算都有單獨的API接口來提供。
NSString *decimalNumberMutiplyWithString(NSString *multiplierValue,NSString *multiplicandValue) { NSDecimalNumber *multiplierNumber = [NSDecimalNumber decimalNumberWithString:multiplierValue]; NSDecimalNumber *multiplicandNumber = [NSDecimalNumber decimalNumberWithString:multiplicandValue]; NSDecimalNumber *product = [multiplicandNumber decimalNumberByMultiplyingBy:multiplierNumber]; return [product stringValue]; }
NSLog(@"%@",decimalNumberMutiplyWithString([NSString stringWithFormat:@"%f",a], [NSString stringWithFormat:@"%d",b])); |
只是測試,所以接口名大致寫寫,名字取得比較不那么講究,希望可以表達清楚。
總的來說,對於貨幣計算,應該需要注意精度的問題。同時在運算的時候,應該優先選用框架提供的API,否則,就應該使用足夠精度的類型運算,同時對自己寫的接口進行足夠的說明,要求開發者按照規范來使用。
在自己不能保證足夠准確的情況下,用適當的說明的要求來規避責任還是可以接受的。至少被人抱怨兩句總比出錯強。