浮点数运算精度问题

关于浮点数精度问题我们首先引入一个案例,如下:

NSInteger index = (NSInteger)(2.6 / 0.2);

请问结果是多少? 13吗?恭喜你,打错了,正确答案是12.其实理解这个问题不难,只是在开发的过程很容被忽略从导致程序运行出现bug.

分析问题的原因:

几乎所有的编程语言都采用了 IEEE-745 浮点数表示法,任何使用二进制浮点数的编程语言都会有这个问题. 数字是 64 位双精度浮点数。IEEE-745规范定义了浮点数的格式,对于64位的浮点数在内存中的表示,最高的1位是符号位,接着的11位是指数,剩下的52位为有效数字,具体:

  • 第0位:符号位, s 表示 ,0表示正数,1表示负数;

  • 第1位到第11位:储存指数部分, e 表示 ;

  • 第12位到第63位:储存小数部分(即有效数字),f 表示

    比如在 JavaScript 中计算 0.1 + 0.2时,到底发生了什么呢? 首先,十进制的0.1和0.2都会被转换成二进制,但由于浮点数用二进制表达时是无穷的,例如。

1
2
0.1 -> 0.0001100110011001...(无限)
0.2 -> 0.0011001100110011...(无限)

IEEE 754 标准的 64 位双精度浮点数的小数部分最多支持 53 位二进制位,所以两者相加之后得到二进制为

1
2
0.0100110011001100110011001100110011001100110011001100     
1

因浮点数小数位的限制而截断的二进制数字,再转换为十进制,就成了 0.30000000000000004。所以在进行算术计算时会产生误差。

解决办法

其实OC中使用NSDecimalNumber可以很简单的避免这个问题, 例如:

  NSDecimalNumber *one = [NSDecimalNumber decimalNumberWithString:@"2.6"];

  NSDecimalNumber *two = [NSDecimalNumber decimalNumberWithString:@"0.2"];

  NSDecimalNumber *result = [one decimalNumberByDividingBy:two];

  NSLog(@"%@",result.stringValue);

另外

如果是一些简单的需求, 可以参照一下

1、浮点数的四舍五入:

1
2
CGFloat num = 5.567;
NSLog(@"%.2f",num);

2、浮点数取整(舍弃后面的小数部分):

1
2
3
CGFloat num = 5.567;
int intNum = (int)num;
NSLog(@"%d",intNum);

3、浮点数向下取整:

1
2
3
4
//高斯函数
CGFloat num = 5.567;
int intNum = floorf(num);
NSLog(@"%d",intNum); 

4、浮点数向上取整:

1
2
3
4
//ceil函数
CGFloat num = 5.567;
int intNum = ceilf(num);
NSLog(@"%d",intNum);