浮点数的二进位表达方法

浮点数的二进位表达方法

浮点运算知识点

小数二进制表达

与整数的二进制表达相同我们可以假设任意小数的二进制为 1011.0011也就是说,按照跟整数转换相同的思路我们可以换算出

 1*2^(3) + 0*2^(2) + 1*2^(1) +1*2^(0) + 0*2^(-1) + 0*2^(-2) + 1*2^(-3) + 1*2^(4) = 11.1875

不过像1011.0011这种表达式是给人看得,真正在计算机内传输,是运用更高效率的科学记号方式,更进一步说是运用了正则表达式与ECXESS系统,毕竟使用上述二进制表示方法,不仅没有告诉计算机小数点前后各有几位,在传统32bits的浮点数下能表示的範围也有限,所以该方法仅限于让人们清楚意识到,小数位跟整数位一样,是使用2的次方数作为进位依据,差别仅在于所使用为负数

小数转换误差

我们从上一张图可以看出一个问题,那就是小数位的表达是存在一个特定误差範围的,例如若要表示0.1(10进制),但是1* 2^(-1) = 0.5、1* 2^(-2) = 0.25、1* 2^(-3) = 0.125、1* 2^(-4) = 0.0625...

不管我们怎么分配始终无法完美取得0.1,这也是计算机在小数位上存在的既定误差,另一方面0.1转换成二进制会等于0.0001100110011...无限循环,永远无法求得準确值,计算机最后只能想出折衷方案取四捨五入或直接取到固定位数

浮点数表达式

浮点数表达式是计算机内用来表示小数的方法,由IEEE规定组成格式,并用有32 bits单精度(float)与64 bits双精度(double)的资料型态

浮点数的表达方法主要由4个参数组成,分别是正负符号、尾数(m)、基数(n)、指数(e) :

那么问题来了,二进制的浮点数该怎么用上述科学记号方式表达成二进位的0和1进而让计算机能够读懂? 换个角度想,我们把数字改成10进制,我们先就整数而言,比如说12500 :

10进位整数位中符号为正,尾数为1.25、基数为10、指数为4,这种方式是科学记号约定俗成的表达方式,其中重点在尾数应该表示为一个大于等于1,小于10的一组小数,因为是10进位,每增加一个位就必须乘以10,所以基数应当表达为10,指数表达进位数

要将12500转换成浮点表达事前必须先概括介绍使用正则表达式与Excess系统的目的

正则表达式

计算机系统中用来表示浮点数尾数的方法

Excess系统

计算机系统中用来表示指数的的方法

使用这种类似科学记号的表达方式有效增加能容纳位数,以及更便于计算机进行运算,因为最终目的是要使计算机能够读懂12500,更精确地说就是0和1的组合

可能很多人会直接将12500转换成二进制 = 0011 0000 1101 0100,作为运算结果,不过浮点数的表达方式与整数是完全不同的,所以这种转换是错误的,我们来看一下IEEE是怎么规定浮点数的二进位表达方式 :

先釐清最简单的部分,举float为例,12500为正数,所以符号位是0,计算机是使用二进制单位表示,所以基数为2

指数我们需要使用Excess系统,该系统会将例如8 bits的指数位(0~255)取中间值作为0,也就是说127

0111 1111代表指数为0,二进制的127~255依序分别是0、1、2 ... 128,相反的二进制的126~0分别对应-1、-2 ... -127

12500存在于2^(13) = 81922到2^(14)=16384之间,所以我们求得12500的指数为13,因为使用Excess系统故加上127=140,再将140转换成二进制可以得到1000 1100,这就是8位指数位

而正则表达式就像上面的小数转换表一样将赋值成1的位数呈上2^(-n)次方然后依序相加,需要注意最后需要加上1 * 2^(0),这是IEEE规定的浮点数格式,你可以想像科学记号也对于尾数的规定,在浮点数中我们使用的是小数点前固定为1的正则表达式,目的是凑出一个大于等于1小于2的一个小数位与指数位相乘进而得出近似值

因为+1是一个约定俗成,人尽皆知的概念,所以在真正填充二进位值的时候可以省略掉1这个栏位,空出来的空位刚好可以填充多一个小数位增加精度(虽然也不会增加多少,毕竟都乘到2^(-23))

回到12500的例子,转成二进制等于 011 0000 1101 0100,经过右移将小数点前一位製作成1,最后在将小数点后捕到23位,该值就是12500的浮点数尾数,但是因为浮点数的正则表达规範,我们可以省略掉小数位前的1

不过在真实的案例中我们不太需要去编写API去刻意转换浮点数的二进位表达,因为当我们在宣告一个浮点变数时,计算机会自动将指标指向内存4 bytes区块,并给定使用浮点表达式所生成的二进制码资料初始值

程式案例

void float_to_hex(float a){    float num = a;    char buffer[40] = {0};    unsigned int get_hex;    int i,j=0;     get_hex = *(unsigned int*)&a; // hex必须要用unsigned int来取值    printf("hex: %0x\n", get_hex);    for(i = 0; i < 32; i++){        if(i == 1 || i == 9){            buffer[j++] = '-';        }        if((get_hex >> (32-(i+1)))%2 == 1)            buffer[j++] = '1';        else            buffer[j++] = '0';    }    buffer[j] = '\0';    printf("bin: %s\n", buffer);    return;}

执行结果

如何消除小数误差

在前文我们提到计算机在做浮点运算时,会产生些微误差,比如说0.1连续加10次,理论上应该要为10,但是永远不可能

float a=0;for(int i = 1 ; i <= 10 ; i++){    a += 0.1

另外一种误差错误发生在利用浮点作为判断依据,这种写法很危险,假如小数位发生错误,整个程式将会卡在无线迴圈中,所以一般我们不会使用浮点数作为判断式的判断子

float a=0;while(1){    a += 0.02;    printf("%f\n", a);    if(a == 4) // loop        break;}

要解决浮点数造成的问题,可以试着把浮点数转成整数来计算,不是直接casting,而是想办法藉由运算消除小数部分(例如乘以1000后运算),运算出结果后再将结果转成浮点数,或者因为浮点造成的误差实际上很小,如果对误差容忍度不会太低,那大可忽略不计,例如室外测量温度範围在0~40度,那么0.00001的误差实际上并不影响温度计运作

案例练习

将浮点数转换成16进位形式将16进位转换成浮点数将浮点数转换成字串
// convert float to str#include <stdio.h>#include <stdlib.h>#include <stdint.h>#include <stdbool.h>#include <math.h>float sqrt_float(uint16_t num, int exp){    float number = num;    if(exp == 0)        return 1;    else{        for(int i = 1 ; i < abs(exp) ; i++)            number *= num;    }    if(exp < 0)        number = 1/number;    return number;}uint32_t float_to_hex(float a){    float num = a;    char buffer[40] = {0};    unsigned int get_hex;    int i,j=0;     get_hex = *(unsigned int*)&a; // hex必须要用unsigned int来取值    printf("hex: %0x\n", get_hex);    printf("dec: %0u\n", get_hex);    for(i = 0; i < 32; i++){        if(i == 1 || i == 9){            buffer[j++] = '-';        }        if((get_hex >> (32-(i+1)))%2 == 1)            buffer[j++] = '1';        else            buffer[j++] = '0';    }    buffer[j] = '\0';    printf("bin: %s\n", buffer);    return get_hex;}char *hex_to_float_to_str(uint32_t num, int8_t precision){ // 16进制转浮点再转字串    int j=0, exp, m;     float real_part, remain_part, n, value;    bool negative=false;    char *buffer = (char*)malloc(128);        if((num & 0x80000000) != 0){        negative = true; // 正负号        buffer[j++] = '-';    }    exp = ((num & 0x7f800000) >> 23) - 127; // 取指数    m = ((num & 0x007fffff));    for(uint16_t i = 1 ; i <= 23 ; i++) // 取尾数        n += ((m >> (23 - i))%2) * (1/sqrt_float(2,i));     n++;    value = n*(sqrt_float(2,exp));    // Float to string, since no function in C.    real_part = floor(value);    remain_part = value - real_part;      for(uint16_t i = 1 ; i <= precision ; i++)        remain_part *= 10;    remain_part = floor(remain_part);               if(remain_part < 0) // block '-'        remain_part *= -1;    sprintf(buffer+j, "%.0f.%.0f", real_part, remain_part);        return buffer;}int main(){    float a;    uint32_t test;     char *buffer_p;    printf("Input a float type number: ");    scanf("%f", &a);    test = float_to_hex(a); // float to bin & hex    buffer_p = hex_to_float_to_str(test, 5); // turn hex to float    printf("float to string: %s\n", buffer_p);    return 0;}

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章