关于OC中的小数精确计算---NSDecimalNumber
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的更多相关文章
- java 小数精确计算
小数精确计算 System.out.println(2.00 -1.10);//0.8999999999999999 上面的计算出的结果不是 0.9,而是一连串的小数.问题在于1.1这个数字不能被精确 ...
- jQuery中height()不能精确计算的问题
jQuery中关于高度的计算有三个方法:outerHeight().innerHeight().height() outerHeight():获取元素集合中第一个元素的当前计算高度值,包括paddin ...
- 电商网站中价格的精确计算(使用BigDecimal进行精确运算(实现加减乘除运算))
使用BigDecimal的String的构造器.商业计算中,使用bigdecimal的String构造器,一定要用. 重要的事情说三遍: 商业计算中,使用bigdecimal的String构造器! 商 ...
- js中对小数的计算
在js 的计算中如果涉及到小数的运算,那结果可不要想当然了,比如 0.1+0.2 的计算 var num1 = 0.1; var num2 = 0.2; console.log(num1+num2) ...
- JAVA中精确计算金额BigDecimal
package com.chauvet.utils; import java.math.BigDecimal; import java.text.DecimalFormat; import java. ...
- 不要在精确计算中使用float和double类型
http://blog.csdn.net/androiddevelop/article/details/8478879 一 问题描述 float和double类型不能用于精确计算,其主要目的是为了科 ...
- Java中浮点型数据Float和Double进行精确计算的问题
Java中浮点型数据Float和Double进行精确计算的问题 来源 https://www.cnblogs.com/banxian/p/3781130.html 一.浮点计算中发生精度丢失 ...
- [转] 商业应用中Java浮点数的精确计算及表示
[From] https://blog.csdn.net/stevene/article/details/586089 问题提出 (1).浮点数精确计算 胜利油田三流合一项目中一直存在一个问题,就是每 ...
- Java浮点数float,bigdecimal和double精确计算的精度误差问题总结
(转)Java浮点数float,bigdecimal和double精确计算的精度误差问题总结 1.float整数计算误差 案例:会员积分字段采用float类型,导致计算会员积分时,7位整数的数据计算结 ...
随机推荐
- karma单元测试入门
学习angularjs,都会遇到karma单元测试,可是初学者面对复杂的测试配置往往不知从何入手,下面我们将抛开angularjs,单独使用两个js文件,完成一次测试入门. 0,karma原理
- 窥探Swift编程之错误处理与异常抛出
在Swift 2.0版本中,Swift语言对其错误处理进行了新的设计,当然了,重新设计后的结果使得该错误处理系统用起来更爽.今天博客的主题就是系统的搞一下Swift中的错误处理,以及看一下Swift中 ...
- Oracle层次查询
Oracle层次查询的语法如下: 下面根据两道“烧脑”的题具体来体现: 1. 根据时间先后顺序,十二星座的英文名称用逗号串起来为'Aries,Taurus,Gemini,Cancer,Leo,Virg ...
- C算法编程题(五)“E”的变换
前言 上一篇<C算法编程题(四)上三角> 插几句话,说说最近自己的状态,人家都说程序员经常失眠什么的,但是这几个月来,我从没有失眠过,当然是过了分手那段时期.每天的工作很忙,一个任务接一个 ...
- android view 中各函数的执行顺数
这个就好像是 activity 的生命周期一样,如果我们要使用自定义的 view,那么就很有必要了解一下 view 的那些能够被重写的函数的执行顺序.废话不多讲,以常用的5个函数为例子,见下文: pa ...
- 关于一道数据库例题的解析。为什么σ age>22 (πS_ID,SCORE (SC) ) 选项是错的?
本人大二学子.近段时间在做数据库复习题的时候遇到一道题,如下. 有关系SC(S_ID,C_ID,AGE,SCORE),查找年龄大于22岁的学生的学号和分数,正确的关系代数表达式是( ) . ⅰ. πS ...
- XML文件解析并利用SimpleAdapter将解析结果显示在Activity中
首先创建一个实体类 Mp3Info用来存储解析的XML文件中的内容: public class Mp3Info implements Serializable{ private static fina ...
- gnuplot: 一种更为简洁的曲线,柱状图绘图软件
gnuplot: 一种更为简洁的曲线,柱状图绘图软件 gnuplot: 一种更为简洁的曲线,柱状图绘图软件 Zhong Xiewei Wed Jun 25 gnuplot简单介绍 关于gnuplot的 ...
- cordova加载层、进度条、文件选择插件
在做cordova项目的时候,感觉应用的响应速度跟原生应用比相差甚远,一个主要问题就是如加载层.进度条等弹出对话框的效率不行.毕竟项目中的这些弹框都是用dom拼成的,dom的渲染效率和原生控件比起来慢 ...
- 用Vagrant和Ansible搭建持续交付平台
这是一个关于Vagrant的学习系列,包含如下文章: Vagrant入门 创建自己的Vagrant box 用Vagrant搭建Jenkins构建环境 用Vagrant和Ansible搭建持续交付平台 ...