C#只提供了单精度类型的float,但有时候单精度也显得有点浪费,毕竟一个单精度也需要四个字节。有些语言已经开始支持半精度了,但C#目前还不支持,于是自己写了个float转单精度的函数,此函数将float类型的数据转换成byte[],返回的数组中其实只有两个元素,因为半精度的大小就是两字节。当得到了转换之后的byte[]后,可以把它存到文件里,等需要分析数据的时候,再读取出来,用转换函数转成float类型。采用这种方法,可以节省一半的存储空间,当然也得接受数据精度受损的风险。
从float转换成byte[ ]
static byte[] FloatToHalf(float f)
{
Byte[] bytes = BitConverter.GetBytes(f);
Byte sign = 0x80;
SByte exp;
byte[] myByte = new byte[2];//返回的数组
ushort result;
ushort m;
sign = (Byte)(bytes[3] & sign);//求符号位
//求指数位
exp = (SByte)(bytes[3] << 1);
exp += (SByte)(bytes[2] >> 7);
exp -= 127;
exp += (SByte)((1<<(expSize -1)) -1);
if (exp < 0)//下溢出
exp = 0;
//求尾数
m = (ushort)(bytes[2] & 0x7f);
m = (ushort)(m << (mSize - 7));
m += (ushort)(bytes[1] >> (15-mSize));
if (((bytes[1] >> (15 - mSize - 1)) & 1) == 1)//若被移除的最高位是1,则产生进位。
m += 1;
if (m >= (ushort)Math.Pow(2, mSize))//若进位后发生尾数溢出,则取消进位
m -= 1;
result = sign;
result = (ushort)(result << 8);//把符号位移动到最高位上
//装载指数位
short temp1 = exp;
temp1 = (short)(temp1 << (15-expSize));
result += (ushort)temp1;
result += m;//装载尾数
myByte[0] = (byte)result;
myByte[1] = (byte)(result >> 8);
return myByte;
}
从byte[ ]转换成float的函数
static float HalfToFloat(byte[] myByte)
{
ushort h = myByte[1];
h = (ushort)(h << 8);
h += myByte[0];
int sign = 1;
int exp;
uint m;
float result;
double temp = 0;
if ((h >> 15) == 1)
sign = -1;
exp = h & 0x00007fff;
exp = (exp >> (mSize));//提取指数位
exp -= ((1 << (expSize - 1)) - 1);
m = (uint)(h << (expSize + 1)) >> (expSize + 1);
for(int i = 0;i < mSize; i++)
{
if((m & 1) == 1)
temp += Math.Pow(2, i - mSize);
m = m >> 1;
}
temp += 1;
temp *= Math.Pow(2, exp);
result = (float)temp;
return result * sign;
}
上面的这两个函数中,需要用到两个全局变量
static int expSize = 6;//指数的位数
static int mSize = 15-expSize;//尾数的位数
学习过IEEE 754标准的都知道,以float类型为例,浮点数在计算机的存储中是被分为三部分的,符号位占1个比特,阶码占8个比特,尾数占23个比特。expSize是用来设定阶码(也就是指数)有几个比特,mSize用来设定尾数有几个比特。建议大家在设定的时候,expSize取4~6之间的整数。IEEE 754标准给出的阶码位数是5位。
有问题欢迎留言讨论。