NSDecimalNumber

翻译补充自:http://rypress.com/tutorials/objective-c/data-types/nsdecimalnumber 感谢乐于分享的大神

在iOS开发中,和货币价格计算相关的,需要注意计算精度的问题。即使只是两位小数,也会出现误差。使用float类型运算,是完全不够的。所以我在网上来找寻答案,但是在百度找了好久,没有发现一个好的解决方案,后来发现了NSDecimalNumber这个类,但是国内搜索引擎上的资料用太少了,所以自己通过找资料的方法发现了如下这篇文章.

先叙述下我遇到的问题,我的服务器传给我的是一个float的值,作为一个对外的库,由于存在版本延续,需要保留对外的flaot的类型,不改变API,选择进行内部适配。

问题引出

float a = 0.01;
int b = 99999999;
double c = 0.0;
c = a*b;
NSLog(@"%f",c); //输出结果为 1000000.000000
NSLog(@"%.2f",c); //输出结果为 1000000.00 //明显不够精确

在网上找到了一个国内朋友的博客也遇到和我一样的问题,他尝试了如下两种解决方案

将float强制转换为double

c = a*(double)b;
NSLog(@"%f",c); //输出结果 999999.967648
NSLog(@"%.2f",c); //输出结果 999999.97 // 明显已经丢失精度

通过和NSString的转换,将计算的原始数据转换为纯粹的double类型的数据,这样的计算精度就可以达到要求了

NSString *objA = [NSString stringWithFormat:@"%.2f", a];
NSString *objB = [NSString stringWithFormat:@"%.2f", (double)b];
c = [objA doubleValue] * [objB doubleValue];
NSLog(@"%.2f",c); //输出结果 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])); //输出结果 999999.99

下面开始讲解这个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可预测的 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这个类为OC程序提供了定点算法功能,它被设计为了不会损失精度并且可预先设置凑整规则的10进制计算,这让它成为一个比浮点数(double)更好的选则去表示货币,然而作为交换用NSDecimalNumber计算变得更加复杂

Internally, a fixed-point number is expressed as表示为 sign符号 mantissa尾数 x 10^exponent指数. The sign defines whether it’s positive or negative, the mantissa is an unsigned integer representing the significant有意义的 digits有效数字, and the exponent determines where the decimal小数 point falls in the mantissa.

在内部,一个有小数点的数被表示为上图中的这种形式,这个符号定义了它是正数还是负数,这个尾数是一个无符号的整数用来表示有效数字,这个指数决定了小数点在尾数中的位置

It’s possible to对...是可能的 manually手动地 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"];
对手动地用尾数,指数,符号来装配一个NSDecimalNumber是可能的,但是但是从一个字符串表示转换成一个NSDecimalNumber更容易,以下的片段创建了值15.99用两个方法

Like NSNumber, all NSDecimalNumber objects are immutable不可变的, which means you cannot change their value after they’ve been created.

像NSNumber一样,所有的NSDecimalNumber对象都是不可变额,这意味着在它们创建之后不能改变它们的值

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的主要工作是提供可供选择的定点算法给C的原生算法操作,全部的五个NSDecimalNumber的计算方法在下面被演示

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相对物, 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.

不像它们的相对物浮点,这些操作保证了精确性,然而,你会注意到有很多超出计算结果的额外小数位,根据这个应用,它们可能会也可能不会令人满意(例如,你可能想约束货币值只有2个小数位),这是为什么自定义进位行为被引入的原因

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:

每一个在上文中的计算方法有一个替换物---behavior:下面列出了让你定义这个操作凑整这个结果的值,这个类封装了一个特别的凑整行为,可以被实例化如下:
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选项 are NSRoundPlain, NSRoundDown, and NSRoundBankers, all of which are defined by NSRoundingMode. The scale: parameter参数 defines the number of decimal places the resulting value should have, and the rest of其余的 the parameters参数 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属性使所有的操作算到最近的位置,其他的进位选项是NSRoundPlain, NSRoundDown, 和 NSRoundBankers,它们都被定义在NSRoundingMode,scale参数定义了结果值保留的小数位的数量,其余的参数给所有的操作定义了异常处理行为,这这个例子中,NSDecimalNumber将只捕获一个异常,如果你尝试除0.

This rounding behavior can then be passed to the decimalNumberByMultiplyingBy:withBehavior: method (or any of the other arithmetic methods), as shown below.

这个凑整的行为可以在之后被调用通过decimalNumberByMultiplyingBy:withBehavior:这个方法(或者任何其他的计算方法),如下所示.
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.

现在,代替36.882,这个total算到2个小数位,结果是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存储于 in different instances. For example:

像NSNumber, NSDecimalNumber对象应该用compare:方法代替原生的不等式操作,此外,这确保了值被比较,即使他们存储于不通的实例中,例如
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实用 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优先选择 for high-performance性能 applications dealing with处理 a large number of calculations.

对于大多数实用的目的,NSDecimalNumber应该能满足你定点的需要,然而,值得注意的是也有一个基于纯C语言的基础函数,它相对面向对象编程提供了效率在上面的讨论中,因此我们优先选择它为了一个高性能的应用处理一个大数的计算

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相应的 factory工厂 method, also shown below.

代替NSDecimalNumber对象,C实例创建了一个NSDecimal结构体,不幸的,Foundation Framework没有使它很容易的创建从scratch,你需要去生成一个从一个成熟的NSDecimalNumber用它的decimalValue方法,它是一个相应的工厂方法,也被展示如下
NSDecimalNumber *price = [NSDecimalNumber decimalNumberWithString:@"15.99"];
NSDecimal asStruct = [price decimalValue];
NSDecimalNumber *asNewObject = [NSDecimalNumber decimalNumberWithDecimal:asStruct];

This isn’t exactly准确的 an ideal 理想 way to create NSDecimal’s, but once you have a struct representation of your initial初始 values, you can stick to坚持 the functional API presented below. All of these functions use struct’s as inputs and outputs.

它不是一个准确的理想的方法去创建一个NSDecimal’s,但是一旦你有一个结构展现了你的初始值,你可以一直坚持这个功能API被提出,所有的函数用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.

代替计算方法的是NSDecimalNumber,C的接口用函数像NSDecimalAdd(), NSDecimalSubtract()等.代替结果的返回值,这个函数填入了第一个参数用一个可计算的值,这使它可以重用一个存在的NSDecimal在几个操作,避免分配不必要的结构体仅仅是为了保存媒介值

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.

例如,以下的片段用一个结果变量被函数调用了5次,和算法节每一次计算都创建一个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固有的,与生俱来的 part of each operation—it’s not encapsulated in a separate分开 entity单独实体 like NSDecimalNumberHandler.

主意到这些函数接受一个NSDecimal结构体的引用,这是为什么我们需要用一个取址符(&)代替直接使用它们,也主意到凑整是每一个操作固有的一部分,它没有像NSDecimalNumberHandler被封装在一个分开的单独实体中

The NSLocale instance defines the formatting格式化 of NSDecimalString(), and is discussed讨论 more thoroughly彻底 in the Dates module.

NSLocale实例定义了NSDecimalString的格式化,讨论的更彻底在日期模块中

Error Checking

Unlike their OOP counterparts相对物, the arithmetic functions don’t raise exceptions when a calculation error occurs发生. 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.

不想它们的相对物面向对象编程,这个计算函数在计算错误发生时不会捕获异常,代替的是,它们允许普通的C模式用一个返回值去表明成功或者失败,所有上文的函数返回了一个NSCalculationError,它定义了发生了什么错误,这个可能的情景如下
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的工作正是面向对象编程的实例,除非你用NSDecimalCompare()这个函数
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个翻墙后找到的资源

http://ios.eezytutorials.com/nsdecimalnumber-by-example.php

关于OC中的小数精确计算---NSDecimalNumber的更多相关文章

  1. java 小数精确计算

    小数精确计算 System.out.println(2.00 -1.10);//0.8999999999999999 上面的计算出的结果不是 0.9,而是一连串的小数.问题在于1.1这个数字不能被精确 ...

  2. jQuery中height()不能精确计算的问题

    jQuery中关于高度的计算有三个方法:outerHeight().innerHeight().height() outerHeight():获取元素集合中第一个元素的当前计算高度值,包括paddin ...

  3. 电商网站中价格的精确计算(使用BigDecimal进行精确运算(实现加减乘除运算))

    使用BigDecimal的String的构造器.商业计算中,使用bigdecimal的String构造器,一定要用. 重要的事情说三遍: 商业计算中,使用bigdecimal的String构造器! 商 ...

  4. js中对小数的计算

    在js 的计算中如果涉及到小数的运算,那结果可不要想当然了,比如  0.1+0.2 的计算 var num1 = 0.1; var num2 = 0.2; console.log(num1+num2) ...

  5. JAVA中精确计算金额BigDecimal

    package com.chauvet.utils; import java.math.BigDecimal; import java.text.DecimalFormat; import java. ...

  6. 不要在精确计算中使用float和double类型

    http://blog.csdn.net/androiddevelop/article/details/8478879 一  问题描述 float和double类型不能用于精确计算,其主要目的是为了科 ...

  7. Java中浮点型数据Float和Double进行精确计算的问题

    Java中浮点型数据Float和Double进行精确计算的问题 来源  https://www.cnblogs.com/banxian/p/3781130.html 一.浮点计算中发生精度丢失     ...

  8. [转] 商业应用中Java浮点数的精确计算及表示

    [From] https://blog.csdn.net/stevene/article/details/586089 问题提出 (1).浮点数精确计算 胜利油田三流合一项目中一直存在一个问题,就是每 ...

  9. Java浮点数float,bigdecimal和double精确计算的精度误差问题总结

    (转)Java浮点数float,bigdecimal和double精确计算的精度误差问题总结 1.float整数计算误差 案例:会员积分字段采用float类型,导致计算会员积分时,7位整数的数据计算结 ...

随机推荐

  1. EntityFramework 外键值映射

    如果在 EF OnModelCreating 中配置了实体外键映射,也就是 SQL Server 中的 ForeignKey,那么我们在添加实体的时候,主实体的主键值会自动映射到子实体的外键值,并且这 ...

  2. java 线程池ThreadPoolExecutor 如何与 AsyncTask() 组合使用。

    转载请声明出处谢谢!http://www.cnblogs.com/linguanh/ 这里主要使用Executors中的4种静态创建线程池实例方法中的 newFixedThreadPool()来举例讲 ...

  3. 1Z0-053 争议题目解析706

    1Z0-053 争议题目解析706 考试科目:1Z0-053 题库版本:V13.02 题库中原题为: 706.You execute the following command to set the ...

  4. 关于《selenium2自动测试实战--基于Python语言》

    关于本书的类型: 首先在我看来技术书分为两类,一类是“思想”,一类是“操作手册”. 对于思想类的书,一般作者有很多年经验积累,这类书需要细读与品位.高手读了会深有体会,豁然开朗.新手读了不止所云,甚至 ...

  5. centos7 mysql数据库安装和配置

    一.系统环境 yum update升级以后的系统版本为 [root@yl-web yl]# cat /etc/redhat-release CentOS Linux release 7.1.1503 ...

  6. 使用 gpg 加密文件 - 通过 shell 或 php

    使用 gpg 加密文件 - 通过 shell 或 php 背景:客户提供私钥,并要求我方通过php把加密后的文件传输给他们. 环境 macOS Sierra 10.12.1 php 7.0.8 0.安 ...

  7. Date, TimeZone, MongoDB, java中date的时区问题

    打印new Date(),Fri Aug 12 13:37:51 CST 2016. 显示Asia/Shanghai的时区,但是date toString 的时区简写却是CST.更坑爹的是,Googl ...

  8. ADO.net 更新和插入数据 遇到null 执行不成功

    首先交代下背景,遇到一个问题:SqlCommand新增记录时,参数为null时,运行并不报错,只是返回(0),也就是更新失败. 在用C#往数据库里面插入记录的时候, 可能有的字段我们并不赋值(有可能是 ...

  9. [开源ORM] SqliteSugar 3.x .net Core版本成功上线

    SqliteSqlSugar 3.X API 作为支持.NET CORE 为数不多的ORM之一,除了具有优越的性能外,还拥有强大的功能,不只是满足你的增,删,查和改.实质上拥有更多你想像不到的功能,当 ...

  10. 创建ASP.NET Core MVC应用程序(5)-添加查询功能 & 新字段

    创建ASP.NET Core MVC应用程序(5)-添加查询功能 & 新字段 添加查询功能 本文将实现通过Name查询用户信息. 首先更新GetAll方法以启用查询: public async ...