iOS开发浮点数计算精度问题

1、浮点数运算带来的问题

CGFloat badnum = 1.05f;
NSLog(@"badnumX100 = %f",badnum*100);
//输出
//badnumX100 = 104.999995  

在日常工作中涉及到浮点数(float、double)的运算

2、浮点数运算精度的解决方案

NSDecimalNumber的实现

数字19.99表示方法

#define NSDecimalMaxSize (8)
    // Give a precision of at least 38 decimal digits, 128 binary positions.

#define NSDecimalNoScale SHRT_MAX

typedef struct {
    signed   int _exponent:8;//幂指数
    unsigned int _length:4;     // length == 0 && isNegative -> NaN
    unsigned int _isNegative:1;//符号
    unsigned int _isCompact:1;
    unsigned int _reserved:18;
    unsigned short _mantissa[NSDecimalMaxSize];//存储数据
} NSDecimal;

使用NSDecimalNumber进行浮点数的运算

    //100.0转化成NSDecimalNumber
    NSDecimalNumber *g_100 = [NSDecimalNumber decimalNumberWithString:@"100"];
    //1.05转化成NSDecimalNumber
    NSDecimalNumber *g_105 = [NSDecimalNumber decimalNumberWithString:@"1.05"];
    //两个数相乘 1.05X100
    NSDecimalNumber *goodnum = [g_105 decimalNumberByMultiplyingBy:g_100];
    NSLog(@"goodnum 1.05X100 = %@",goodnum);

    //输出
    //goodnum 1.05X100 = 105

浮点数判等

由于浮点数内部存储地不精确,在比较两个浮点数是否相等时,不能简单地使用 == 符号来判断。
判断两个浮点数 A, B 是否相等,需要转化成求这两个浮点数差的绝对值 C,即 C = fabs(A - B),然后看这个值 C 是否小于一个极小数。
如果小于一个极小数,则可以认为这两个浮点数是相等的。
根据实际工程中的需要,通常这个极小数的参考值是 1e-6 或 1e-8 。

3、浮点数在计算机中的存储方式导致精度问题

浮点数在计算机中的存储方式

不论是 float 类型还是 double 类型,在存储方式上都是遵从IEEE的规范:

float 遵从的是 IEEE R32.24;double 遵从的是 IEEE R64.53;

单精度或双精度在存储中,都分为三个部分:

符号位 (Sign):0代表正数,1代表为负数;
指数位 (Exponent):用于存储科学计数法中的指数数据;
尾数部分 (Mantissa):采用移位存储尾数部分;

单精度和双精度的存储方式.png

R32.24 和 R64.53 的存储方式都是用科学计数法来存储数据的,比如:

8.25 用十进制表示为:8.25 X 1 0 0 10^0 100
120.5 用十进制表示为:1.205 X 1 0 2 10^2 102

而计算机根本不认识十进制的数据,他只认识0和1。所以在计算机存储中,首先要将上面的数更改为二进制的科学计数法表示:

8.25 用二进制表示为:1000.01 可以表示为1.0001 X 2 3 2^3 23
120.5 用二进制表示为:1110110.1 可以表示为1.1101101 X 2 6 2^6 26

任何一个数的科学计数法表示都为1. xxx * 2n ,尾数部分就可以表示为xxxx,由于第一位都是1,所以将小数点前面的1省略。由此,23bit的尾数部分,可以表示的精度却变成了24bit,道理就是在这里。

对于指数部分,因为指数可正可负(占1位),所以8位的指数位能表示的指数范围就只能用7位,范围是:-127至128。所以指数部分的存储采用移位存储,存储的数据为元数据 加上 127

元数据 加上 127

“指数”从00000000开始(表示-127)至11111111(表示+128)
所以,10000000表示指数1 (127 + 1 = 128 --> 10000000 ) ;
指数为 3,则为 127 + 3 = 130,表示为 01111111 + 11 = 10000010 ;

8.25二进制存储.png

120.5二进制存储.png
二进制反推出浮点数:
如下内存数据:01000010111011010000000000000000,
将该数据分段:0  10000101  11011010000000000000000
image
计算出这样一组数据表示为:

1101101*10(133-127=6) =1.1101101 * 2= 1110110.1=120.5

2.2和2.25的区别

单精度的 2.2 转换为双精度后,精确到小数点后13位之后变为了2.2000000476837
而单精度的 2.25 转换为双精度后,变为了2.2500000000000

2.25** 的单精度存储方式表示为:**0 10000001 00100000000000000000000

2.25** 的双精度存储方式表示为:**0 10000000 0010010000000000000000000000000000000000000000000000000

这样 2.25 在进行强制转换的时候,数值是不会变的。

**将十进制的小数转换为二进制的小数的方法是:****将小数*2****,取整数部分。**

   0.2×2=0.4,所以二进制小数第一位为0.4的整数部分0;

   0.4×2=0.8,第二位为0.8的整数部分0;

   0.8×2=1.6,第三位为1;

   0.6×2=1.2,第四位为1;

   0.2×2=0.4,第五位为0;

   ...... 这样永远也不可能乘到=1.0,得到的二进制是一个无限循环的排列 00110011001100110011...

对于单精度数据来说,尾数只能表示 24bit 的精度,所以2.2的 float 存储为:

2.2的二进制存储

但是这种存储方式,换算成十进制的值,却不会是2.2。

因为在十进制转换为二进制的时候可能会不准确(如:2.2),这样就导致了误差问题!

并且 double 类型的数据也存在同样的问题!

所以在浮点数表示中,都可能会不可避免的产生些许误差!

在单精度转换为双精度的时候,也会存在同样的误差问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值