美文网首页iOS 的那些事儿
iOS开发浮点数计算精度问题

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

作者: Pandakingli | 来源:发表于2019-02-17 19:39 被阅读0次

    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 10^0
    120.5 用十进制表示为:1.205 X 10^2

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

    8.25 用二进制表示为:1000.01 可以表示为1.0001 X2^3
    120.5 用二进制表示为:1110110.1 可以表示为1.1101101 X2^6

    任何一个数的科学计数法表示都为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 * 26 = 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 类型的数据也存在同样的问题!

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

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

    相关文章

      网友评论

        本文标题:iOS开发浮点数计算精度问题

        本文链接:https://www.haomeiwen.com/subject/bpopeqtx.html