1位符号位+5位指数位+10位尾数位,共16位,内存占2个字节
具体fp16表示法可以参照:机器学习-fp16表示
运算步骤
- 检查操作数中是否有0、Inf、NaN
NaN * a = Nan;
Inf * 0 = Nan;
(-Inf) * 0 = Nan;
Inf * (+/-)a = (+/-)Inf;
-Inf * (+/-)a = (-/+)Inf;
0 * a = 0; - 计算指数
两指数直接相加:exp1 - bias + exp2 - bias = exp1 + exp2 - 2 * bias - 计算尾数,并判断是否产生进位
两尾数直接相乘 - Round to nearest even舍入处理
舍入到最接近且可以表示的值,当存在两个数一样接近时,取偶数值。(如2.4舍入为2,2.6舍入为3;2.5舍入为2,1.5舍入为2。)
当存在两个数一样接近时取偶数值是因为由于其他舍入方式均令结果单方向偏移,导致在运算时出现较大的统计偏差。而采用这种偏移则50%的机会偏移两端方向,从而减少偏差。 - 最终结果溢出判断
由于规格化可能导致阶码发生溢出,如果没有溢出,则直接返回结果;如果发生向上溢出,则返回Inf;如果发生向下溢出,则返回0
//判断是否为无效数:阶码全1,尾数非全0,则为无效数
int isNan(int data)
{
int exp = ((data & 0x7fff) >> 10) & 0x1f;
int mant = data & 0x3ff;
return ((exp == 0x1f) && (mant != 0)) ? 1 : 0;
}
//判断是否为正负无穷:阶码全1,尾数全0,则为无穷大,符号位为0,则为正无穷,符号位为1,则为负无穷
int isInf(int data)
{
int exp = ((data & 0x7fff) >> 10) & 0x1f;
int mant = data & 0x3ff;
return ((exp == 0x1f) && (mant == 0)) ? 1 : 0;
}
//判断是否为0:阶码全0,尾数全0,则为0,符号位为0,则为正零,符号位为1,则为负零
int isZero(int data)
{
int exp = ((data & 0x7fff) >> 10) & 0x1f;
int mant = data & 0x3ff;
return ((exp == 0) && (mant == 0)) ? 1 : 0;
}
int* fp16_Mul(int* data1, int* data2)
{
unsigned int mant1, mant2, mantNew; //data1尾数,data2尾数,相乘之后的尾数
unsigned int mantToExp; //尾数累加有进位
int sign1, sign2, signNew; //data1符号位,data2符号位,乘积符号位
int exp1, exp2, expNew; //data1阶码,data2阶码,乘积阶码
int tenBit = 0, elevenBit = 0, lastBit = 0;
int fp16Res;
sign1 = (data1 >> 15) & 0x01;
sign2 = (data2 >> 15) & 0x01;
exp1 = ((data1 & 0x7fff) >> 10) & 0x1f;
exp2 = ((data2 & 0x7fff) >> 10) & 0x1f;
mant1 = data1 & 0x3ff;
mant2 = data2 & 0x3ff;
signNew = sign1 ^ sign2;
//1. 检查操作数中是否有0、Inf、NaN
if(isNan(data1) || isNan(data2)){
return 0x7c01; //NAN和任何数相乘为NAN
}
if(isInf(data1)){
if(isZero(data2)){ //(+/-)Inf * 0 = NAN
return 0x7c01;
}
else if(signNew == 1){ //Inf * a
return 0xfc00; //-Inf
}
else if(signNew == 0){
return 0x7c00; //+Inf
}
}
if(isInf(data2)){
if(isZero(data1)){ //(+/-)Inf * 0 = NAN
return 0x7c01;
}
else if(signNew == 1){ //Inf * a
return 0xfc00; //-Inf
}
else if(signNew == 0){
return 0x7c00; //+Inf
}
}
if(isZero(data1) || isZero(data2))
return 0;
//2.计算expNew
expNew = (exp1 - 15) + (exp2 - 15) + 15;
//3.计算mantNew
mant1 = mant1 | 0x400;
mant2 = mant2 | 0x400;
mantNew = mant1 * mant2;
//判断是否产生进位
if(mantNew & 0x200000){ //11位乘11位最多产生22位
expNew++; //阶码+1
lastBit = mantNew & 0x01; //最后一位是否为1
mantNew >>= 1; //尾数右移
}
//最后21位小数舍去后10位,保留前11位(其中包括1位前导数1)
//4.Round to nearest even,因为浮点数无法精确表示所有数值,所以要进行舍入处理。
tenBit = (mantNew >> 10) & 0x01; //第10位尾数是否为1(从左往右数)
elevenBit= (mantNew >> 9) & 0x01; //第11位尾数是否为1
lastBit= (mantNew & 0x1ff) || lastBit; //11位之后也就是后9位是否存在1
/*舍入(以保留2位小数为例帮助理解下部分舍入代码,
两种情况入:a). 0.0011或0.00100001,舍入到最近的数,都得0.01;b). 0.0110两边一样近,向偶数舍入得0.1。
两种情况舍:a). 0.010001舍入到最近的数,得0.01;b). 0.0010两边一样近,向偶数舍入得0.00)
*/
if(elevenBit && (tenBit || lastBit)){
mantNew += 0x400; //舍入,有效的前11位尾数,+1
}
if(mantNew & 0x200000){ //舍入之后产生进位
expNew++;
mantNew >>= 1;
}
mantNew >= 10; //舍去21位的后10位
//溢出判断
if(expNew >= 31){ //上溢出
if(signNew == 1){
return 0xfc00; //返回负无穷
}
else{
return 0x7c00; //返回正无穷
}
}
if(expNew <= 0) //下溢出
{
return 0;
}
fp16Res = (((signNew & 0x01) << 15) | ((expNew & 0x1f) << 10) | (mantNew & 0x3ff));
return fp16Res;
/*
输入
data1:254 (0x5bf0, 0101 1011 1111 0000, 1.111111 * 2^(22-15));
data2:7.6836 (0x47af, 0100 0111 1010 1111, 1.11 1010 1111 * 2^(17-15));
输出:
fp16Res:261.75(0x5c17, 0101 1100 0001 0111, 1.00 0001 0111 * 2^(23-15));