iOS中的高精度数值计算

前言

在iOS开发中,和价格计算相关的,需要注意计算精度的问题,使用float、double来计算价格数值会出现精度损失,使用官方的NSDecimalNumber是更好的选择。

本篇文章探讨下浮点数的精度损失,以及在iOS中高精度计算的处理

1.浮点数的表示

现在所有通用计算机都采用IEEE 754来表示浮点数, IEEE二进制浮点数算术标准是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。

IEEE 754规定,一个浮点数可以表示成如下形式**
image

32位的单精度浮点数,最高1位是符号位s,接着的8位是指数E,剩下的23位是有效数字M
64位的双精度浮点数,最高1位是符号位s,接着的11位是指数E,剩下的52位为有效数字M


image

以float举例

image

float的存储正是将四字节的32位划分成了三部分,分别是:

Sign(1位):用来表示浮点数是正数还是负数,0表示正数,1表示负数。

Exponent(8位):阶码或者称为指数,用移码表示(将每一个数值加上一个偏置常数 bias,当编码位数为n时,IEEE 754标准的bias取 (2^n-1)-1,单精度为为127, 双精度为1023,把阶码的值调整到一个无符号数的范围内以便进行比较) ,全0或全1用来表示特殊值。其真实值,单精度要减去127,双精度减去1023

Mantissa(23位):尾数部分,1≤M<2,即M可以写成1.xxxxx的形式,其中xxxxx表示小数部分。IEEE 754规定小数点前总为1,所以在内部保存时隐含表示,省一位。

十进制向二进制的转换

  1. 十进制整数转换为二进制整数
    十进制整数转换为二进制整数采用"除2取余,逆序排列"法。具体做法是:用2去除十进制整数,可以得到一个商和余数;再用2去除商,又会得到一个商和余数,如此进行,直到商为零时为止,然后把先得到的余数作为二进制数的低位有效位,后得到的余数作为二进制数的高位有效位,依次排列起来。
    把 173 转换为二进制数。
image.png

2.十进制小数转换为二进制小数
十进制小数转换成二进制小数采用"乘2取整,顺序排列"法。具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数 部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,或者达到所要求的精度为止。
然后把取出的整数部分按顺序排列起来,先取的整数作为二进制小数的高位有效位,后取的整数作为低位有效位。

把 0.8125 转换为二进制小数。

image.png

浮点数和真值转换的两个例子

一、已知float型变量x的机器数为BEE00000H,求x的值是多少?
1 01111101 11000000000000000000000 (末尾补零)

按照(-1)^s x (mantissa) x 2^(Exponent-127)
数符:1 负数
阶码:二进制的01111101转换到十进制为125 ,125-127=-2
尾数部分的值:1+2^(-1) +2^(-2) = 1+0.5 +0.25 = 1.75

所以真值为-1.75 * 2^(-2) = - 0.4375

二、已知float型变量x的值为-12.75,求x的机器数是多少?

-12.75 = -1100.11B
= -1.10011 * 2^3

所以符号位s为1,阶码E = 127+3 = 128+2 = 1000 0010 显式显示的部分尾数:100 1100 0000 0000 0000 0000
x的机器数表示:1 10000010 100 1100 0000 0000 0000 0000

舍入

因为表示方法限制了浮点数的精度和范围,所以浮点数只能近似的表示实数运算。对于值x,我们一般想用一种系统的方法找到“最接近的”匹配值,它可以用期望的浮点形式表示出来。这就是舍入运算的任务。IEEE浮点格式定义了四种不同的舍入方式,默认的方法是找到最接近的匹配,而其他三种可用于计算上界和下界。
四种舍入方式 :
向偶数舍入(默认)
向零舍入
向下舍入
向上舍入

浮点数的精度误差的原因

了解了浮点数的结构,总结一下精度误差原因

  1. float类型占32位,一共有2^32种组合, double类型占64位,一共有2^64种组合, 而实数是无穷的,当输入的数据是不可表示数时,会将其转化为最邻近的可表示数

2.有些十进制数,无法用有限的二进制浮点数表示。 比如0.1,转换为二进制 0.0 0011 0011 0011...无限循环,当你声明float m = 0.1时,其存储的数值精度已经发生了变化

2.iOS中高精度计算的处理

很多语言中都有相应的高精度计算方式,如Java中的BigDecimal类,C#中的decimal类型都是用来解决高精度计算问题的。Objective-C中提供的有一个NSDecimalNumber类,用来处理精确计算。

NSDecimalNumber是NSNumber的一个不可变子类,创建之后不能改变它们的值。 它提供了一个面向对象的包装器来做base-10算术。实例可以表示任何可以用mantissa x 10^exponent表示的数字,其中mantissa是一个长度不超过38位的十进制整数,exponent是一个从-128到127的整数。

其表示的值 value = sign mantissa*10^exponent
sign:符号位,定义了它是正数还是负数,
mantissa:尾数,unsigned long long类型
exponent:指数,决定了小数点在尾数中的位置

比如 15.99,
NSDecimalNumber price = [NSDecimalNumber decimalNumberWithMantissa:1599 exponent:-2 isNegative:NO];
NSDecimalNumber
price = [NSDecimalNumber decimalNumberWithString:@"15.99"];
可以使用字符串或者手动装配mantissa、exponent、sign的构造方法来生成一个NSDecimalNumber实例

基本的算术方法

加法
- (NSDecimalNumber *)decimalNumberByAdding:(NSDecimalNumber *)decimalNumber;

减法
- (NSDecimalNumber *)decimalNumberBySubtracting:(NSDecimalNumber *)decimalNumber;

乘法
- (NSDecimalNumber *)decimalNumberByMultiplyingBy:(NSDecimalNumber *)decimalNumber;

除法
- (NSDecimalNumber *)decimalNumberByDividingBy:(NSDecimalNumber *)decimalNumber;

幂次方
-(NSDecimalNumber*)decimalNumberByRaisingToPower:(NSUInteger)power;

指数
-(NSDecimalNumber*)decimalNumberByMultiplyingByPowerOf10:(short)power;
image

自定义处理行为

- (instancetype)initWithRoundingMode:(NSRoundingMode)roundingMode scale:(short)scale raiseOnExactness:(BOOL)exact raiseOnOverflow:(BOOL)overflow raiseOnUnderflow:(BOOL)underflow raiseOnDivideByZero:(BOOL)divideByZero NS_DESIGNATED_INITIALIZER;

+ (instancetype)decimalNumberHandlerWithRoundingMode:(NSRoundingMode)roundingMode scale:(short)scale raiseOnExactness:(BOOL)exact raiseOnOverflow:(BOOL)overflow raiseOnUnderflow:(BOOL)underflow raiseOnDivideByZero:(BOOL)divideByZero;

// 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.
 // Up 1.2 1.3 1.3 1.4 1.3
 // Bankers 1.2 1.2 1.2 1.4 1.3

roundingMode 要使用的舍入模式,有四种值: 
NSRoundUp, 向上舍入
NSRoundDown, 向下舍入
NSRoundPlain  四舍五入;当被夹在两个正数中间时,取整;当被夹在两个负数之间时,四舍五入。
NSRoundBankers  四舍五入;当为中间值时,将数值向上或向下舍入,使得结果的最低有效数字是偶数。

NSRoundBankers比较特殊,保留位数后一位的数字为5时,根据前一位的奇偶性决定。为偶时向下舍入,为奇数时向上舍入。如:1.25保留一位小数。5之前是2偶数向下取整1.2;1.35保留一位小数时。5之前为3奇数,向上取整1.4。
关于向偶数舍入,摘自csapp第二章 IEEE浮点部分:有什么理由偏向取偶数呢?为什么不始终把位于两个可表示的中间的值都向上舍入?假想一种场景,这组方法舍入一组数值,会在计算这些值的平均数中引入统计偏差。向上舍入得到的一组数的平均值比这些数平均值偏高,向下舍入的话,得到的平均值比这些数的平均值略低一些。 向偶数舍入在大多数现实情况避免了这种统计偏差。在50%的时间里向上舍入,50%的时间里向下舍入。

scale 结果保留几位小数

raiseOnExactness
如果为YES,在发生精确错误的情况下,处理程序将引发异常,否则它将忽略该错误并将控制权返回给调用方法。

raiseOnOverflow
如果为YES,在发生溢出错误时,处理程序将引发异常,否则它将忽略该错误并将控制权返回给调用方法

raiseOnUnderflow
如果为YES,则在发生下溢错误时,处理程序将引发异常,否则它将忽略该错误并将控制权返回给调用方法

raiseOnDivideByZero
如果为YES,则在出现除零错误时,处理程序将引发异常,否则它将忽略该错误并将控制权返回给调用方法

decimalNumber的比较

- (NSComparisonResult)compare:(NSNumber *)decimalNumber;

参考链接

计算机系统基础(一):程序的表示、转换与链接
计算机组成原理(唐朔飞)
书籍《深入理解计算机系统》
关于OC中的小数精确计算
十进制小数转换为二进制
浮点数之迷

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容