在iOS開(kāi)發(fā)中抡四,和貨幣價(jià)格計(jì)算相關(guān)的,需要注意計(jì)算精度的問(wèn)題。即使只是兩位小數(shù)指巡,也會(huì)出現(xiàn)誤差淑履。使用float類(lèi)型運(yùn)算,是完全不夠的藻雪。所以我在網(wǎng)上來(lái)找尋答案,但是在百度找了好久,沒(méi)有發(fā)現(xiàn)一個(gè)好的解決方案,后來(lái)發(fā)現(xiàn)了NSDecimalNumber這個(gè)類(lèi),但是國(guó)內(nèi)搜索引擎上的資料用太少了,所以自己通過(guò)找資料的方法發(fā)現(xiàn)了如下這篇文章.
先敘述下我遇到的問(wèn)題,我的服務(wù)器傳給我的是一個(gè)float的值,作為一個(gè)對(duì)外的庫(kù)秘噪,由于存在版本延續(xù),需要保留對(duì)外的flaot的類(lèi)型阔涉,不改變API,選擇進(jìn)行內(nèi)部適配捷绒。
問(wèn)題引出
float a = 0.01;int b = 99999999;double c = 0.0;c = a*b;NSLog(@"%f",c); //輸出結(jié)果為 1000000.000000NSLog(@"%.2f",c); //輸出結(jié)果為 1000000.00//明顯不夠精確
在網(wǎng)上找到了一個(gè)國(guó)內(nèi)朋友的博客也遇到和我一樣的問(wèn)題,他嘗試了如下兩種解決方案
將float強(qiáng)制轉(zhuǎn)換為double
c = a*(double)b;NSLog(@"%f",c); //輸出結(jié)果 999999.967648NSLog(@"%.2f",c); //輸出結(jié)果 999999.97// 明顯已經(jīng)丟失精度
通過(guò)和NSString的轉(zhuǎn)換瑰排,將計(jì)算的原始數(shù)據(jù)轉(zhuǎn)換為純粹的double類(lèi)型的數(shù)據(jù),這樣的計(jì)算精度就可以達(dá)到要求了
NSString *objA = [NSString stringWithFormat:@"%.2f", a];NSString *objB = [NSString stringWithFormat:@"%.2f", (double)b];c = [objA doubleValue] * [objB doubleValue];NSLog(@"%.2f",c); //輸出結(jié)果 999999.99
最終方案
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]));//輸出結(jié)果 999999.99
下面開(kāi)始講解這個(gè)NSDecimalNumber
The NSDecimalNumber class provides fixed-point arithmetic算法
capabilities功能
to Objective-C programs. They’re designed to perform base-10 calculations without loss of precision精度
and with predictable可預(yù)測(cè)的
rounding湊整
behavior. This makes it a better choice for representing表示
currency貨幣
than floating-point data types like double. However, the trade-off is that they are more complicated to work with.
NSDecimalNumber這個(gè)類(lèi)為OC程序提供了定點(diǎn)算法功能,它被設(shè)計(jì)為了不會(huì)損失精度并且可預(yù)先設(shè)置湊整規(guī)則的10進(jìn)制計(jì)算,這讓它成為一個(gè)比浮點(diǎn)數(shù)(double)更好的選則去表示貨幣,然而作為交換用NSDecimalNumber計(jì)算變得更加復(fù)雜
Internally, a fixed-point number is expressed as表示為
sign符號(hào)
mantissa尾數(shù)
x 10^exponent指數(shù)
. The sign defines whether it’s positive or negative, the mantissa is an unsigned integer representing the significant有意義的
digits有效數(shù)字
, and the exponent determines where the decimal小數(shù)
point falls in the mantissa.
在內(nèi)部,一個(gè)有小數(shù)點(diǎn)的數(shù)被表示為上圖中的這種形式,這個(gè)符號(hào)定義了它是正數(shù)還是負(fù)數(shù),這個(gè)尾數(shù)是一個(gè)無(wú)符號(hào)的整數(shù)用來(lái)表示有效數(shù)字,這個(gè)指數(shù)決定了小數(shù)點(diǎn)在尾數(shù)中的位置
It’s possible to對(duì)...是可能的
manually手動(dòng)地
assemble裝配
an NSDecimalNumber from a mantissa, exponent, and sign, but it’s often easier to convert it from a string representation表示
. The following snippet片段
creates the value 15.99 using both methods.
NSDecimalNumber *price; price = [NSDecimalNumber decimalNumberWithMantissa:1599 exponent:-2 isNegative:NO]; price = [NSDecimalNumber decimalNumberWithString:@"15.99"];
對(duì)手動(dòng)地用尾數(shù),指數(shù),符號(hào)來(lái)裝配一個(gè)NSDecimalNumber是可能的,但是但是從一個(gè)字符串表示轉(zhuǎn)換成一個(gè)NSDecimalNumber更容易,以下的片段創(chuàng)建了值15.99用兩個(gè)方法
Like NSNumber, all NSDecimalNumber objects are immutable不可變的
, which means you cannot change their value after they’ve been created.
像NSNumber一樣,所有的NSDecimalNumber對(duì)象都是不可變額,這意味著在它們創(chuàng)建之后不能改變它們的值
Arithmetic算法
The main job of NSDecimalNumber is to provide fixed-point alternatives可供選擇的事物
to C’s native原生
arithmetic operations操作
. All five of NSDecimalNumber’s arithmetic methods are demonstrated演示
below在...下
.
NSDecimalNumber的主要工作是提供可供選擇的定點(diǎn)算法給C的原生算法操作,全部的五個(gè)NSDecimalNumber的計(jì)算方法在下面被演示
NSDecimalNumber *price1 = [NSDecimalNumber decimalNumberWithString:@"15.99"]; NSDecimalNumber *price2 = [NSDecimalNumber decimalNumberWithString:@"29.99"]; NSDecimalNumber *coupon = [NSDecimalNumber decimalNumberWithString:@"5.00"]; NSDecimalNumber *discount = [NSDecimalNumber decimalNumberWithString:@".90"]; NSDecimalNumber *numProducts = [NSDecimalNumber decimalNumberWithString:@"2.0"]; NSDecimalNumber *subtotal = [price1 decimalNumberByAdding:price2]; NSDecimalNumber *afterCoupon = [subtotal decimalNumberBySubtracting:coupon]; NSDecimalNumber *afterDiscount = [afterCoupon decimalNumberByMultiplyingBy:discount]; NSDecimalNumber *average = [afterDiscount decimalNumberByDividingBy:numProducts]; NSDecimalNumber *averageSquared = [average decimalNumberByRaisingToPower:2]; NSLog(@"Subtotal: %@", subtotal); // 45.98 NSLog(@"After coupon: %@", afterCoupon); // 40.98 NSLog((@"After discount: %@"), afterDiscount); // 36.882 NSLog(@"Average price per product: %@", average); // 18.441 NSLog(@"Average price squared: %@", averageSquared); // 340.070481
Unlike their floating-point counterparts相對(duì)物
, these operations are guaranteed保證
to be accurate精確
. However, you’ll notice that many of the above calculations result in extra decimal places. Depending on the application, this may or may not be desirable (e.g., you might want to constrain約束
currency values to 2 decimal places). This is where custom rounding湊整
behavior comes in.
不像它們的相對(duì)物浮點(diǎn),這些操作保證了精確性,然而,你會(huì)注意到有很多超出計(jì)算結(jié)果的額外小數(shù)位,根據(jù)這個(gè)應(yīng)用,它們可能會(huì)也可能不會(huì)令人滿意(例如,你可能想約束貨幣值只有2個(gè)小數(shù)位),這是為什么自定義進(jìn)位行為被引入的原因
Rounding Behavior
// Rounding policies : // Original // value 1.2 1.21 1.25 1.35 1.27 // Plain 1.2 1.2 1.3 1.4 1.3 // Down 1.2 1.2 1.2 1.3 1.2 // Up 1.2 1.3 1.3 1.4 1.3 // Bankers 1.2 1.2 1.2 1.4 1.3
Each of the above arithmetic methods have an alternate替換物
withBehavior: form that let you define how the operation rounds the resulting value. The NSDecimalNumberHandler class encapsulates封裝
a particular多有的,特別的
rounding behavior and can be instantiated as follows:
每一個(gè)在上文中的計(jì)算方法有一個(gè)替換物---behavior:下面列出了讓你定義這個(gè)操作湊整這個(gè)結(jié)果的值,這個(gè)類(lèi)封裝了一個(gè)特別的湊整行為,可以被實(shí)例化如下:
NSDecimalNumberHandler *roundUp = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundUp scale:2 raiseOnExactness:NO raiseOnOverflow:NO raiseOnUnderflow:NO raiseOnDivideByZero:YES];
The NSRoundUp argument屬性
makes all operations round up to the nearest place. Other rounding options選項(xiàng)
are NSRoundPlain, NSRoundDown, and NSRoundBankers, all of which are defined by NSRoundingMode. The scale: parameter參數(shù)
defines the number of decimal places the resulting value should have, and the rest of其余的
the parameters參數(shù)
define the exception-handling behavior of any operations. In this case, NSDecimalNumber will only raise an exception if you try to divide by zero.
NSRoundUp屬性使所有的操作算到最近的位置,其他的進(jìn)位選項(xiàng)是NSRoundPlain, NSRoundDown, 和 NSRoundBankers,它們都被定義在NSRoundingMode,scale參數(shù)定義了結(jié)果值保留的小數(shù)位的數(shù)量,其余的參數(shù)給所有的操作定義了異常處理行為,這這個(gè)例子中,NSDecimalNumber將只捕獲一個(gè)異常,如果你嘗試除0.
This rounding behavior can then be passed to the decimalNumberByMultiplyingBy:withBehavior: method (or any of the other arithmetic methods), as shown below.
這個(gè)湊整的行為可以在之后被調(diào)用通過(guò)decimalNumberByMultiplyingBy:withBehavior:這個(gè)方法(或者任何其他的計(jì)算方法),如下所示.
NSDecimalNumber *subtotal = [NSDecimalNumber decimalNumberWithString:@"40.98"];NSDecimalNumber *discount = [NSDecimalNumber decimalNumberWithString:@".90"];NSDecimalNumber *total = [subtotal decimalNumberByMultiplyingBy:discount withBehavior:roundUp];NSLog(@"Rounded total: %@", total);
Now, instead of 36.882, the total gets rounded up to two decimal points, resulting in 36.89.
現(xiàn)在,代替36.882,這個(gè)total算到2個(gè)小數(shù)位,結(jié)果是36.89
Comparing NSDecimalNumbers
Like NSNumber, NSDecimalNumber objects should use the compare: method instead of the native inequality不等
operators. Again, this ensures that values are compared, even if they are stored存儲(chǔ)于
in different instances. For example:
像NSNumber, NSDecimalNumber對(duì)象應(yīng)該用compare:方法代替原生的不等式操作,此外,這確保了值被比較,即使他們存儲(chǔ)于不通的實(shí)例中,例如
NSDecimalNumber*discount1 = [NSDecimalNumber decimalNumberWithString:@".85"]; NSDecimalNumber*discount2 = [NSDecimalNumber decimalNumberWithString:@".9"]; NSComparisonResult result = [discount1 compare:discount2]; if (result ==NSOrderedAscending) { NSLog(@"85%% < 90%%小于"); } else if (result == NSOrderedSame) { NSLog(@"85%% == 90%%等于");} else if (result ==NSOrderedDescending) { NSLog(@"85%% > 90%%大于");}
NSDecimalNumber also inherits繼承
the isEqualToNumber: method from NSNumber.
NSDecimalNumber也從NSNumber中繼承了isEqualToNumber:
Decimal Numbers in C
For most practical實(shí)用
purposes目的
, the NSDecimalNumber class should satisfy滿足
your fixed-point needs; however, it’s worth noting that there is also a function-based alternative available可用
in pure純
C. This provides increased efficiency效率
over the OOP interface discussed above and is thus preferred優(yōu)先選擇
for high-performance性能
applications dealing with處理
a large number of calculations.
對(duì)于大多數(shù)實(shí)用的目的,NSDecimalNumber應(yīng)該能滿足你定點(diǎn)的需要,然而,值得注意的是也有一個(gè)基于純C語(yǔ)言的基礎(chǔ)函數(shù),它相對(duì)面向?qū)ο缶幊烫峁┝诵试谏厦娴挠懻撝?因此我們優(yōu)先選擇它為了一個(gè)高性能的應(yīng)用處理一個(gè)大數(shù)的計(jì)算
NSDecimal
Instead of an NSDecimalNumber object, the C interface is built around the NSDecimal struct. Unfortunately, the Foundation Framework doesn’t make it easy to create an NSDecimal from scratch. You need to generate生成
one from a full-fledged成熟的
NSDecimalNumber using its decimalValue method. There is a corresponding相應(yīng)的
factory工廠
method, also shown below.
代替NSDecimalNumber對(duì)象,C實(shí)例創(chuàng)建了一個(gè)NSDecimal結(jié)構(gòu)體,不幸的,Foundation Framework沒(méi)有使它很容易的創(chuàng)建從scratch,你需要去生成一個(gè)從一個(gè)成熟的NSDecimalNumber用它的decimalValue方法,它是一個(gè)相應(yīng)的工廠方法,也被展示如下
NSDecimalNumber *price = [NSDecimalNumber decimalNumberWithString:@"15.99"];NSDecimal asStruct = [price decimalValue];NSDecimalNumber *asNewObject = [NSDecimalNumber decimalNumberWithDecimal:asStruct];
This isn’t exactly準(zhǔn)確的
an ideal 理想
way to create NSDecimal’s, but once you have a struct representation of your initial初始
values, you can stick to堅(jiān)持
the functional API presented below. All of these functions use struct’s as inputs and outputs.
它不是一個(gè)準(zhǔn)確的理想的方法去創(chuàng)建一個(gè)NSDecimal’s,但是一旦你有一個(gè)結(jié)構(gòu)展現(xiàn)了你的初始值,你可以一直堅(jiān)持這個(gè)功能API被提出,所有的函數(shù)用struct作為輸入和輸出
Arithmetic Functions
In lieu of代替
the arithmetic methods of NSDecimalNumber, the C interface uses functions like NSDecimalAdd(), NSDecimalSubtract(), etc. Instead of returning the result, these functions populate填入
the first argument with the calculated value. This makes it possible to reuse an existing NSDecimal in several operations and avoid allocating分配
unnecessary structs just to hold intermediary媒介
values.
代替計(jì)算方法的是NSDecimalNumber,C的接口用函數(shù)像NSDecimalAdd(), NSDecimalSubtract()等.代替結(jié)果的返回值,這個(gè)函數(shù)填入了第一個(gè)參數(shù)用一個(gè)可計(jì)算的值,這使它可以重用一個(gè)存在的NSDecimal在幾個(gè)操作,避免分配不必要的結(jié)構(gòu)體僅僅是為了保存媒介值
For example, the following snippet片段
uses a single result variable across 5 function calls. Compare this to the Arithmetic section, which created a new NSDecimalNumber object for each calculation.
例如,以下的片段用???個(gè)結(jié)果變量被函數(shù)調(diào)用了5次,和算法節(jié)每一次計(jì)算都創(chuàng)建一個(gè)NSDecimalNumber做比較,
NSDecimal price1 = [[NSDecimalNumber decimalNumberWithString:@"15.99"] decimalValue];NSDecimal price2 = [[NSDecimalNumber decimalNumberWithString:@"29.99"] decimalValue];NSDecimal coupon = [[NSDecimalNumber decimalNumberWithString:@"5.00"] decimalValue];NSDecimal discount = [[NSDecimalNumber decimalNumberWithString:@".90"] decimalValue];NSDecimal numProducts = [[NSDecimalNumber decimalNumberWithString:@"2.0"] decimalValue]NSLocale *locale = [NSLocale currentLocale];NSDecimal result;NSDecimalAdd(&result, &price1, &price2, NSRoundUp);NSLog(@"Subtotal: %@", NSDecimalString(&result, locale));NSDecimalSubtract(&result, &result, &coupon, NSRoundUp);NSLog(@"After coupon: %@", NSDecimalString(&result, locale));NSDecimalMultiply(&result, &result, &discount, NSRoundUp);NSLog(@"After discount: %@", NSDecimalString(&result, locale));NSDecimalDivide(&result, &result, &numProducts, NSRoundUp);NSLog(@"Average price per product: %@", NSDecimalString(&result, locale));NSDecimalPower(&result, &result, 2, NSRoundUp);NSLog(@"Average price squared: %@", NSDecimalString(&result, locale));
Notice that these functions accept references to NSDecimal structs, which is why we need to use the reference operator (&) instead of passing them directly. Also note that rounding is an inherent固有的,與生俱來(lái)的
part of each operation—it’s not encapsulated in a separate分開(kāi)
entity單獨(dú)實(shí)體
like NSDecimalNumberHandler.
主意到這些函數(shù)接受一個(gè)NSDecimal結(jié)構(gòu)體的引用,這是為什么我們需要用一個(gè)取址符(&)代替直接使用它們,也主意到湊整是每一個(gè)操作固有的一部分,它沒(méi)有像NSDecimalNumberHandler被封裝在一個(gè)分開(kāi)的單獨(dú)實(shí)體中
The NSLocale instance defines the formatting格式化
of NSDecimalString(), and is discussed討論
more thoroughly徹底
in the Dates module.
NSLocale實(shí)例定義了NSDecimalString的格式化,討論的更徹底在日期模塊中
Error Checking
Unlike their OOP counterparts相對(duì)物
, the arithmetic functions don’t raise exceptions when a calculation error occurs發(fā)生
. Instead, they follow the common C pattern of using the return value to indicate表明,象征
success or failure. All of the above上文的
functions return an NSCalculationError, which defines what kind of error occurred. The potential可能的
scenarios情景
are demonstrated演示
below.
不想它們的相對(duì)物面向?qū)ο缶幊?這個(gè)計(jì)算函數(shù)在計(jì)算錯(cuò)誤發(fā)生時(shí)不會(huì)捕獲異常,代替的是,它們?cè)试S普通的C模式用一個(gè)返回值去表明成功或者失敗,所有上文的函數(shù)返回了一個(gè)NSCalculationError,它定義了發(fā)生了什么錯(cuò)誤,這個(gè)可能的情景如下
NSDecimal a = [[NSDecimalNumber decimalNumberWithString:@"1.0"] decimalValue];NSDecimal b = [[NSDecimalNumber decimalNumberWithString:@"0.0"] decimalValue];NSDecimal result;NSCalculationError success = NSDecimalDivide(&result, &a, &b, NSRoundPlain);switch (success) { case NSCalculationNoError: NSLog(@"Operation successful"); break; case NSCalculationLossOfPrecision: NSLog(@"Error: Operation resulted in loss of precision"); break; case NSCalculationUnderflow: NSLog(@"Error: Operation resulted in underflow"); break; case NSCalculationOverflow: NSLog(@"Error: Operation resulted in overflow"); break; case NSCalculationDivideByZero: NSLog(@"Error: Tried to divide by zero"); break; default: break;}
Comparing NSDecimals
Comparing NSDecimal’s works exactly正是
like the OOP interface, except you use the NSDecimalCompare() function:
比較NSDecimals的工作正是面向?qū)ο缶幊痰膶?shí)例,除非你用NSDecimalCompare()這個(gè)函數(shù)
NSDecimal discount1 = [[NSDecimalNumber decimalNumberWithString:@".85"] decimalValue];NSDecimal discount2 = [[NSDecimalNumber decimalNumberWithString:@".9"] decimalValue];NSComparisonResult result = NSDecimalCompare(&discount1, &discount2);if (result == NSOrderedAscending) { NSLog(@"85%% < 90%%");} else if (result == NSOrderedSame) { NSLog(@"85%% == 90%%");} else if (result == NSOrderedDescending) { NSLog(@"85%% > 90%%");}
附上1個(gè)FQ后找到的資源
http://ios.eezytutorials.com/nsdecimalnumber-by-example.php