浮点数单精度32b与半精度16b转换C语言IEEE 754

IEEE 754-标准二进制算术浮点数

(1) 单精度(32位)浮点数的结构:
在这里插入图片描述

名称 长度 比特位置

符号位 Sign (S) : 1bit (b31)
指数部分Exponent (E) : 8bit (b30-b23)
尾数部分Mantissa (M) : 23bit (b22-b0)

其中的指数部分(E)采用的偏置码(biased)的形式来表示正负指数,若E<127则为负的指数,否则为非负的指数。

注意:%f输出float类型,输出6位小数,有效位数一般为7位;


(2) 双精度(64位)浮点数的结构:
在这里插入图片描述
名称 长度 比特位置

符号位 Sign (S) : 1bit (b63)
指数部分Exponent (E) : 11bit (b62-b52)
尾数部分Mantissa (M) : 52bit (b51-b0)

双精度的指数部分(E)采用的偏置码为1023

求值方法:(-1)S*(1.M)*2(E-1023) (公式2)

注意:双精度数也可用%f格式输出,它的有效位一般为16位,给出小数6位。(这一点在计算金额的时候尤为重要,超过有效位的数字是无意义的,一般会出错。)


(3) 半精度(16位)浮点数的结构:

在这里插入图片描述

名称 长度 比特位置

符号位 Sign (S) : 1bit (b15)
指数部分Exponent (E) : 5bit (b14-b10)
尾数部分Mantissa (M) : 10bit (b9-b0)

最近还诞生了一种Bfloat16的计数方式,使用和半精度相同的位数,实现了保持和单精度一样的指数位也就是8位指数位,可以表示和单精度相同的数字范围,但是牺牲了小数位也就是精度。

半精度浮点数是一种计算机使用的二进制浮点数数据类型。半精度浮点数使用2字节(16位)存储。在IEEE 754-2008中,它被称作binary16。这种类型只适合用来存储那些对精度要求不高的数字,而不适合用来计算。
半精度浮点数是一种相对较新的浮点类型。 英伟达在2002年初发布的Cg语言中将它定义为 half 数据类型,并且首次在2002年末发布的GeForce FX中实现。ILM当时正在寻找一种能够有高动态范围,并且不需要过多消耗硬盘以及内存,而且能像单精度浮点数和双精度浮点数那样被用来进行浮点计算的图像格式。由SGI的John Airey领导的硬件加速可编程着色小组在1997年发明了作为’bali’设计工作的一部分的s10e5数据类型。这曾在SIGGRAPH2000年的论文中介绍过。(见章节 4.3)并且在美国专利7518615中被进一步记录。
半精度浮点数可以在包括OpenEXR,JPEG XR,OpenGL,Cg语言和D3DX等数种计算机图形环境中使用。与8位或16位整数的相比,它的优点是可以提升动态范围,从而使高对比度图片中更多细节得以保留。与单精度浮点数相比,它的优点是只需要一半的存储空间和带宽(但是会以精度和数值范围为代价)
`


Float32ToFloat16:

可以把32的float IEEE754规范转化为16位int值

short FloatToFloat16( float value )
{
    short   fltInt16;
    int     fltInt32;
    memcpy( &fltInt32, &value, sizeof( float ) );
    fltInt16    =  ((fltInt32 & 0x7fffffff) >> 13) - (0x38000000 >> 13);
    fltInt16    |= ((fltInt32 & 0x80000000) >> 16);

    return fltInt16;
}

Float16ToFloat32:

可以把16位的float IEEE754规范的int值转成32位的float

float Float16ToFloat( short fltInt16 )
{
    int fltInt32    =  ((fltInt16 & 0x8000) << 16);
    fltInt32        |= ((fltInt16 & 0x7fff) << 13) + 0x38000000;

    float fRet;
    memcpy( &fRet, &fltInt32, sizeof( float ) );
    return fRet;
 }

按NVidia方案:

把单精度浮点数转成半精度,可以这么做:
half Float2Half(float m)
{
    unsigned long m2 = *(unsigned long*)(&m);    // 强制把float转为unsigned long
    // 截取后23位尾数,右移13位,剩余10位;符号位直接右移16位;
    // 指数位麻烦一些,截取指数的8位先右移13位(左边多出3位不管了)
    // 之前是0~255表示-127~128, 调整之后变成0~31表示-15~16
    // 因此要减去127-15=112(在左移10位的位置).
    unsigned short t = ((m2 & 0x007fffff) >> 13) | ((m2 & 0x80000000) >> 16) 
        | (((m2 & 0x7f800000) >> 13) - (112 << 10));           
    if(m2 & 0x1000) 
        t++;                       // 四舍五入(尾数被截掉部分的最高位为1, 则尾数剩余部分+1)
    half h = *(half*)(&t);     // 强制转为half
    return h ;
}

从半精度转回单精度比较好办, 按格式取出符号位、指数和尾数,再按定义计算,结果保存为float即可。

float Half2Float(half n)
{
    unsigned short frac = (n & 0x3ff) | 0x400;
    int exp = ((n & 0x7c00) >> 10) - 25;
    float m;
 
    if(frac == 0 && exp == 0x1f)
        m = INFINITY;
    else if (frac || exp)
        m = frac * pow(2, exp);
    else
        m = 0;
 
    return (n & 0x8000) ? -m : m;
}

最后把实际数据从单精度转成半精度,再转回单精度,计算误差:

    for(float n = 4e-5; n < 6e4; n *= 1.001) { 
        printf("%f, %f,   %.4fn", n, Half2Float(Float2Half(n)), 
                ((double)n - Half2Float(Float2Half(n))) / n * 100.0); 
    }

实测最大误差0.048%(也就是1/2048),平均绝对误差0.018%,似乎还不错。

Google TensorFlow的方案验证起来就非常简单了,砍掉后16位即可,四舍五入还是要的。

#include <stdio.h>
 
int main(void)
{
    for(float n = 1e-8; n < 1e8; n *= 1.001) {
        unsigned long k, l;
        k = *(unsigned long*)(&n);
        l = k & 0xffff0000;
        if(k & 0x8000)
            l += 0x10000;                  // 四舍五入
        float m = *(float*)(&l);
 
        printf("%f, %f, %fn", n, m, (n - m) / n);
    }
    return 0;
}

最大误差0.39%(也就是1/256),平均绝对误差0.14%。许多场合其实主要关心的只是数量级,用这个也不错。
参考 https://blog.csdn.net/weixin_39985279/article/details/111367291;

结果:

0.05 mag=4CCCCD经过转换后为0.049988 mag=4CC000;

m数值 -->0.049988 ,n数值 -->0.050000
float16int存储结果(HEX) -->2A66
m占用字节(Byte)–> 4 ,a占用字节(Byte) --> 2
二进制 --> 10101001100110
float:f=0.049988
sign=0,exp=7A,mag=4CC000
float:f2=0.049988

m数值 -->0.049988 ,n数值 -->0.050000
float16int存储结果(HEX) -->2A66
m占用字节(Byte)–> 4 ,a占用字节(Byte) --> 2
二进制 --> 10101001100110
float:f=0.050000
sign=0,exp=7A,mag=4CCCCD
float:f2=0.050000

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

happy_baymax

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值