IEEE 754是一种定义了浮点运算标准的国际标准,其规定了两种浮点数格式:单精度浮点数(32位)和双精度浮点数(64位)。
单精度浮点数
IEEE 754单精度浮点数采用32位二进制表示,其中符号位占1位,指数部分占8位,尾数部分占23位。具体结构如下:
其中,
“S”表示符号位,取值为0或1,代表正数或负数;
“Exp”为指数部分,使用移码偏置的方式进行编码,其有效范围为-126~127,可以通过对指数字段的值加上偏置数(127)来获得实际的指数值;
“Frac”为尾数部分,其最高位始终为1,因此可以被省略并隐含地表示,因此实际存储的是23+1=24位的尾数。
采用上述编码方式能够使单精度浮点数达到较大有效数字的情况。具体来说,由于尾数部分占据的比例较大,可以表示精度为2^-23的小数值,同时,由于指数部分采用移位偏置的形式,指数可能为负数并表示小于1的小数,所以可以表示的最小非零值的数据范围是2^(-126)(1-2^-23),而最大值可以为(2-2^(-23))×2^(127)。
IEEE 754单精度浮点数可以表示实数的近似值,其最多可以支持7位有效数字,并且存在精度误差问题。
双精度浮点数
IEEE 754双精度浮点数采用64位二进制表示,其中符号位占1位,指数部分占11位,尾数部分占52位。具体结构如下:
其中,“S”表示符号位,取值为0或1,代表正数或负数;“Exp”为指数部分,使用移码偏置的方式进行编码,其有效范围为-1022~1023,可以通过对指数字段的值加上偏置数(1023)来获得实际的指数值;“Frac”为尾数部分,其最高位始终为1,因此可以被省略并隐含地表示,因此实际存储的是52+1=53位的尾数。
由于双精度浮点数具有更高的精度,因此它能够支持更多有效数字,通常可以表示超过15位有效数字的实数值。同时,由于指数部分较长,指数字段可以表示更广泛的数据范围。具体来说,双精度浮点数可以表示的最小非零值为2^(-1022)(1-2^-52),其范围约为±10^-308到±10^308。
综上所述,IEEE 754双精度浮点数拥有更高的精度和更广的取值范围,因此在科学、金融、工程等领域中被广泛使用。由于存在精度误差问题,其最多可以支持15~16位有效数字,应该根据具体情况选择适当的浮点数类型进行计算。
基于C++联合体实现
采用C++中特有的联合体(union)的方式,在同一内存空间中可以同时存储两个不同类型的值,实现这两个不同类型内容的相互访问。
float getFloat(quint32 value)
{
union {
float f;
uint32_t i;
} val;
val.i = value;
return val.f;
}
double getDouble(quint64 value)
{
union {
double d;
uint64_t u;
} res;
res.u = value;
return res.d;
}
uint32_t getU32(float value)
{
union {
float f;
uint32_t i;
} val;
val.f = value;
return val.i;
}
uint64_t getU64(double value)
{
union {
double d;
uint64_t u;
} res;
res.d = value;
return res.u;
}
在Qt环境下编写测试代码:
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//测试ieee754转单精度
qDebug()<<"0x41040000 convert to float is :"<<getFloat(0x41040000);//8.25
qDebug()<<"0x430B4CCC convert to float is :"<<getFloat(0x430B4CCC);//139.3
//测试ieee754转双精度
qDebug()<<"0xBFB0000000000000 convert to double is :"<<getDouble(0xBFB0000000000000);//-0.0625
//测试float转ieee754单精度
qDebug()<<"8.25 convert to u32 is :"<<QString("0x%1").arg(getU32(8.25),8,16).toUpper();
qDebug()<<"139.3 convert to u32 is :"<<QString("0x%1").arg(getU32(139.3),8,16).toUpper();
//测试double转ieee754双精度
qDebug()<<"-0.0625 convert to u64 is :"<<QString("0x%1").arg(getU64(-0.0625),16,16).toUpper();
return a.exec();
}
运行代码后的输出结果:
0x41040000 convert to float is : 8.25
0x430B4CCC convert to float is : 139.3
0xBFB0000000000000 convert to double is : -0.0625
8.25 convert to u32 is : "0X41040000"
139.3 convert to u32 is : "0X430B4CCD"
-0.0625 convert to u64 is : "0XBFB0000000000000"
结论:利用联合体可以实现需要的互相转换功能。
基于ieee754协议实现
下面的代码时基于协议的具体内容,一步一步实现float和double数据解析的代码,有助于对ieee754协议的理解。
float getFloat(uint32_t value)
{
//单精度:V = (-1)^S * (1 + M) * 2 ^(E-127)
//整数用二进制表示后:
//第1位是符号位 s=1表示负数,s=0表示整数
//第2-9位为阶码E
//第10-32位为尾数M
uint value_s = (value >> 31) & 0x01;
uint value_e = (value >> 23) & 0x0FF;
float value_M = 0.0;
for (int i = 0; i < 23; i++)//第10-32位为尾数M
{
int binaryData = (value >> i) % 2; //取value第i位的二进制值
float bitX =static_cast<float>(std::pow(2,(-23+i))); //第i位的二进制值对应的系数
value_M += binaryData*bitX; //系数相加
}
double E_127 = value_e - 127.0; //这里必须减去127.0,否则结果是错误的
float res =static_cast<float>((std::pow(-1,value_s) * (1+value_M) * std::pow(2, E_127)));
return res;
}
double getDouble(quint64 value)
{
//双精度:V = (-1)^S * (1 + M) * 2 ^(E-1023)
//整数用二进制表示后:
//第1位是符号位 s=1表示负数,s=0表示整数
//第2-12位为阶码E
//第13-64位为尾数M
uint64_t value_s = (value >> 63) & 0x01;
uint64_t value_e = (value >> 52) & 0x7FF;
uint64_t mantissa = value & 0xFFFFFFFFFFFFF;
double value_m = static_cast<double>(mantissa) * std::pow(2, -52);
double e_1023 = static_cast<double>(value_e) - 1023.0;
double res = std::pow(-1, value_s) * (1 + value_m) * std::pow(2, e_1023);
return res;
}
注意事项
在使用IEEE 754单精度浮点数和双精度浮点数时,需要注意以下事项:
-
精度误差问题:浮点数的精度受到位数限制以及二进制存储方式等因素的影响,可能存在精度误差。尤其当进行大量运算或浮点数较小(靠近0)时,这种误差会更加明显。
-
溢出和下溢问题:超过最大表示范围的数将会溢出,而过小的数可能会被下溢为0或者-0。需要根据具体情况对数据进行范围判断和处理。
-
非规格化数表示问题:IEEE 754标准中为了增强表示浮点数的精度,引入了非规格化数的概念。当尾数部分全是零的时候,指数也不一定为0。需要对非规格化数的处理进行特殊考虑。
-
舍入方式问题:浮点数计算运算结果需要舍入,常见的舍入方式有四舍五入、向上取整、向下取整、银行家舍入法等。选择合适的舍入方式可以使计算结果更加准确。
-
浮点数比较问题:由于精度误差的存在,直接进行浮点数的比较可能会产生错误。通常可以采用相对误差、绝对误差或者设定一个误差范围等方法进行比较。
基于以上注意事项,需要根据具体情况选择合适的浮点数类型、舍入方式和比较方法,以达到正确地使用浮点数的目的。