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单精度浮点数和双精度浮点数时,需要注意以下事项:

  1. 精度误差问题:浮点数的精度受到位数限制以及二进制存储方式等因素的影响,可能存在精度误差。尤其当进行大量运算或浮点数较小(靠近0)时,这种误差会更加明显。

  2. 溢出和下溢问题:超过最大表示范围的数将会溢出,而过小的数可能会被下溢为0或者-0。需要根据具体情况对数据进行范围判断和处理。

  3. 非规格化数表示问题:IEEE 754标准中为了增强表示浮点数的精度,引入了非规格化数的概念。当尾数部分全是零的时候,指数也不一定为0。需要对非规格化数的处理进行特殊考虑。

  4. 舍入方式问题:浮点数计算运算结果需要舍入,常见的舍入方式有四舍五入、向上取整、向下取整、银行家舍入法等。选择合适的舍入方式可以使计算结果更加准确。

  5. 浮点数比较问题:由于精度误差的存在,直接进行浮点数的比较可能会产生错误。通常可以采用相对误差、绝对误差或者设定一个误差范围等方法进行比较。

基于以上注意事项,需要根据具体情况选择合适的浮点数类型、舍入方式和比较方法,以达到正确地使用浮点数的目的。

06-12 10:37