1. 浮点数原理简介

1.1 IEEE浮点表示

  • IEEE浮点标准用V=(-1)^s * M * 2^E的形式来表示一个数:
  • 符号(sign) s是符号位,1表示负,0表示正
  • 阶码(exponent) E的作用是对浮点数加权,这个权重是2的E次幂,取值范围(单精度-128~127,双精度-1024~1023)
  • 尾数(significand) M是一个二进制小数,它的范围是1~2-ε,或者是0~1-ε

  •  规格化数:阶码的位不全为0也不全为1;此时尾数 M >= 1,它隐含着一个1;
  •  非规格化数:阶码的位全为0;此时尾数0 <= M < 1;即M不隐含一个1。它两个主要用途就是表示0和小于1的数;
  •  特殊值:阶码的位全为1;当小数域全为0,s为1是负无穷,s为0是正无穷;当小数域非0时,结果称为NaN(Not a Number);

1.2 舍入向偶数舍入(或称向最接近的值舍入)是默认的方式,CotexM4中也是

经下面代码测试,发现浮点数转整型数时并没有使用舍入规则,而是直接抛弃小数部分;

但浮点数打印指定位小数时会采用向偶数舍入规则。

void test_float(void)
{

  printf("_float_to_int: %d,%d,%d\r\n",(int32_t)1.40,(int32_t)1.60,(int32_t)1.50);

  printf("_float_: %.2f,%.2f,%.2f,%.2f,%.2f\r\n",1.429999,1.639999,1.2350001,2.505,-1.506);

}

1.3 c语言中int\float\double互相转换

《深入理解计算机系统》 2.4.6 C语言中的浮点数

  • 从int转换成float,数字不会溢出,但是可能被舍入;
  • 从int或float转换成double,不会丢失精度;
  • 从double转换成float,可能溢出成正无穷或负无穷,另外由于精确度较小,还可能被舍入;
  • 从float或者double转换成int,值会向零舍入;当浮点数值超过整数值的最大值时,就会溢出。则正数就可能变成负数。

1.4 浮点数运算

  • 不支持结合性; 如:(3.14+1e10)-1e10 = 0.0;因为舍入,3.14会丢失;若3.14+(1e10-1e10)=3.14; 又如:x=a+b+c和t=a+b;x=t+c;算出来的x值是不一样的;
  • 浮点加法满足单调性属性:如果a>=b,对于任何a,b以及x的值,除了NaN,都有x+a >= x+b。无符号数或补码不具有这个属性。
  • 浮点数乘法在加法上不支持分配性: 例如1e20*(1e20-1e20)=0.0; 而1e201e20-1e201e20会得出NaN。
  • 浮点数乘法支持单调性,同样无符合或补码不支持单调性属性。

1.5 浮点数有效位数

参考C语言浮点数运算_ifreecoding_新浪博客

  • c语言头文件中定义是6位有效位数;
  • c语言书籍中有6~7位的说法;
  • 根据参考里面的推理得出float型能精确地表示6位小数,超过6位小数就有些数不能表示了;如下图

由上图可以看出,float型不能表示1.0000006这个数。而对于很大的数1234567890,它的二进制是0x4E932C06,实际的浮点数值是1234567936,也是高7位对得上。所以6位有效位数是比较精确的一个定义。

2. 浮点数使用注意事项

2.1 浮点数不能直接用等于号做比较

错误示例:

void func(float d1, float d2)
{  
    if(d1 == d2)  
    {    
        ;  
    }
}

正确示例:

#define LIMIT 1.0e-4
void func(float d1, float d2)
{  
    float diff = d1-d2;  
    if(-LIMIT <= diff && diff <= LIMIT)  
    {    
        ;  
    }
}

2.2 浮点数变量不能用于循环中

错误示例:

void func()
{  
    double d;  
    for(d = 0.0; d < 1.0; d += 0.1)  
    {}
}

正确示例:

#define LIMIT 1.0e-4
void func()
{  
    int d;  
    for(d = 0; d < 10; d++)  
    {}
}

2.3 整形数转浮点数的运行时要显示的转换类型

错误示例:

void func()
{  
    int i1, i2;  
    double d;  
    d = i1 / i2;
}

正确示例:

void func()
{  
    int i1, i2;  
    double d;  
    d = (double)i1 / (double)i2;
}

2.4 浮点数常量默认是double型,加后缀F可以声明为float型

示例:

#define F1 3.2F //float型常量数值
#define D1 3.2  //double型常量数值
void fun()
{  
    float f;  
    double d;  
    f = 1.2f * 3.1f;  
    d = 1.2 * 3.1;
}

2.5 CORTEX-M处理器中浮点运算支持

《Cortex-m3和M4权威指南》

  • 不支持双精度浮点数的运行,但可以使用浮点寄存器传输双精度浮点数;
  • 双精度浮点数使用c运行时库函数处理;
  • 有浮点运算单元的MCU中,单精度浮点运算时仍然可能使用c运行时库函数处理;如sinf()和cosf()等函数;
  • 符合IEEE754标准,但没实现所有内容,例如:双精度数据计算;浮点余数;舍入浮点数,转换为整型浮点数;二进制到十进制的互相转换;单精度和双精度数据的直接比较。

3. 定点运算

参考漫谈计算机组成原理(九)定点数及定点数的运算 - 言立慧 - 博客园

《计算机组成原理》-唐塑飞

什么是定点数? 小数点位置固定的数。比如,整型数。

定点数的位移,加减乘除如何实现? 其实就是整型数的位移,加减乘除运算法则。

结论:所以概括来说,定点数运算就是常用的整型数运算。

3.1 使用定点运算代替浮点运算

其实就是使用整型运算代替浮点运算。

举个例子:

void fun()
{  
    uint16_t adc_value = 0;  
    adc_value = 1024;  // 浮点数运算  
    float f_v = 0.0;  
    f_v = (float)adc_value / 4095 * 3.3;  //整型数运算(定点数运算)  
    uint32_t i_v = 0;  
    i_v = adc_value * 1000 / 4095 * 33 / 10;
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐