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
今天的文章单精度浮点型转换为16进制_28.75转换为IEEE短浮点数[通俗易懂]分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/88138.html