0 前言
在完成学校RM嵌入式软件组的作业时遇到了一个问题:将0x00,0x00,0x20,0x40这四个十六进制数据根据一定规则变为浮点数2.5f,我询问AI得知这与IEEE754的浮点数存储规则有关,这篇随笔就来记录一下。
1.IEEE 754简介
IEEE 754为二进制浮点数的表示制定了一个标准,任何一个二进制浮点数 \(x\) 都可以由符号位 \(S\)、尾数 \(M\) 和指数(也叫阶码) \(E\) 表示,即 \(x=(-1)^S\times M \times 2^E\) 。
其中,符号位 \(S\) 为 \(0\) 表示正数,为 \(1\) 表示负数。
尾数 \(M\) 表示一个形如 \(1.0100111\) 的二进制数,IEEE 754标准下所有的尾数都以1开头,所以通常尾数 \(M\) 不存储小数点前的1,这可以多存一位数据,提高精度。
阶码 \(E\) 实际存储了实际指数+偏置值,偏置值在单精度浮点数(float)下表示为127,而在双精度(double)下表示为1023.这样做的目的是方便比较,使得指数大的浮点数,其二进制表示也更大。
类型 | 符号位 | 阶码 | 尾数 | 总位数 |
---|---|---|---|---|
float | 1 | 8 | 23 | 32 |
double | 1 | 11 | 52 | 64 |
上述规则是针对规格值表示的,还有非规格值和非数字两种类型,他们使用不同的规则,此处按下不表。
我们来举个例子:将-12.375转换为IEEE 754单精度格式。
首先确定符号位为1.
然后我们将12.375转换为二进制小数:\((12.375)_{10}=(1100.011)_2\)。
将小数部分表示为二进制下的“科学计数法”的形式:\((1100.011)_2=(1.100011\times 2^3)_2\)。
这样,我们确定了尾数 \(M=(10001100000000000000000)_2\) ,(根据规则,已经舍去小数点前的1,并补零至32位),而实际的指数为3,即阶码 $E=3+127=(130)_{10}=(10000010)_2}。
最后,我们把这三部分缝合起来,就得到了-12.375在IEEE 754下的表示:
点击查看代码
1 10000000 10000000000000000000000
↑ ↑ ↑
| | |
| | +-- 尾数位 (23位): 10001100000000000000000
| +--------- 指数位 (8位): 10000010 = 130
+---------- 符号位 (1位): 1
2.巧用memcpy
函数进行IEEE 754浮点数与十六进制数据的转换
根据上述规则可以发现,将一个浮点数转化为IEEE 754形式的一组二进制数或者十六进制数非常麻烦。但是,由于绝大多数计算机都采用了这个规范,所以一个浮点数在内存中的样子就是满足规范的一组二进制数!如果我们把这个二进制数提取出来,我们就自动完成了转换!
以下是我用deepseek写的转换代码。
点击查看代码
#include <iostream>
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <vector>/*** @brief 将4字节十六进制数据转换为浮点数* @param hexData 4字节的十六进制数据数组(小端序)* @return 对应的浮点数*/
float hexToFloat(const uint8_t hexData[4]) {uint32_t intValue;// 方法1:使用memcpy(推荐,避免严格别名问题)std::memcpy(&intValue, hexData, 4);// 方法2:手动字节组装(小端序)// intValue = (hexData[3] << 24) | (hexData[2] << 16) | (hexData[1] << 8) | hexData[0];// 将整数表示重新解释为浮点数float result;std::memcpy(&result, &intValue, 4);return result;
}/*** @brief 将浮点数转换为4字节十六进制数据(小端序)* @param value 要转换的浮点数* @param hexData 输出参数,存储4字节十六进制数据*/
void floatToHex(float value, uint8_t hexData[4]) {uint32_t intValue;// 将浮点数的位模式复制到整数中std::memcpy(&intValue, &value, 4);// 按小端序提取字节(最低有效字节在前)hexData[0] = (intValue >> 0) & 0xFF; // 最低有效字节hexData[1] = (intValue >> 8) & 0xFF;hexData[2] = (intValue >> 16) & 0xFF;hexData[3] = (intValue >> 24) & 0xFF; // 最高有效字节
}/*** @brief 重载版本:返回十六进制数据向量* @param value 要转换的浮点数* @return 包含4字节十六进制数据的vector*/
std::vector<uint8_t> floatToHex(float value) {std::vector<uint8_t> hexData(4);floatToHex(value, hexData.data());return hexData;
}/*** @brief 打印十六进制数据的详细信息* @param hexData 4字节十六进制数据* @param description 描述信息*/
void printHexData(const uint8_t hexData[4], const std::string& description = "") {if (!description.empty()) {std::cout << description << std::endl;}std::cout << "十六进制: ";for (int i = 0; i < 4; i++) {std::cout << "0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(hexData[i]) << " ";}std::cout << std::dec << std::endl;std::cout << "十进制: ";for (int i = 0; i < 4; i++) {std::cout << static_cast<int>(hexData[i]) << " ";}std::cout << std::endl;
}/*** @brief 详细分析浮点数的IEEE 754表示* @param value 要分析的浮点数*/
void analyzeFloat(float value) {uint32_t intValue;std::memcpy(&intValue, &value, 4);uint32_t sign = (intValue >> 31) & 1;uint32_t exponent = (intValue >> 23) & 0xFF;uint32_t mantissa = intValue & 0x7FFFFF;std::cout << "=== IEEE 754 分析 ===" << std::endl;std::cout << "浮点数: " << value << "f" << std::endl;std::cout << "32位表示: 0x" << std::hex << std::setw(8) << std::setfill('0') << intValue << std::dec << std::endl;std::cout << "二进制: ";for (int i = 31; i >= 0; i--) {std::cout << ((intValue >> i) & 1);if (i == 31) std::cout << " ";if (i == 23) std::cout << " ";}std::cout << std::endl;std::cout << "符号位: " << sign << " (" << (sign ? "负数" : "正数") << ")" << std::endl;std::cout << "指数位: " << exponent << " (偏置后: " << (exponent - 127) << ")" << std::endl;std::cout << "尾数位: 0x" << std::hex << mantissa << std::dec << std::endl << std::endl;
}// 测试函数
void testConversions() {std::cout << "========== 浮点数转十六进制测试 ==========" << std::endl;// 测试用例std::vector<float> testFloats = {3.0f, 2.5f, 1.0f, 0.0f, -1.0f, 3.14159f, -123.456f};for (float value : testFloats) {analyzeFloat(value);// 浮点数转十六进制uint8_t hexData[4];floatToHex(value, hexData);printHexData(hexData, "转换后的十六进制数据:");// 十六进制转回浮点数float converted = hexToFloat(hexData);std::cout << "转换回浮点数: " << converted << "f" << std::endl;// 验证精度std::cout << "精度验证: " << (value == converted ? "✓ 完全匹配" : "✗ 存在误差") << std::endl;std::cout << "----------------------------------------" << std::endl;}
}void testSpecificCases() {std::cout << "\n========== 特定用例测试 ==========" << std::endl;// 测试已知的转换对struct TestCase {float value;uint8_t expectedHex[4];const char* description;};TestCase testCases[] = {{3.0f, {0x00, 0x00, 0x40, 0x40}, "3.0f"},{2.5f, {0x00, 0x00, 0x20, 0x40}, "2.5f"},{1.0f, {0x00, 0x00, 0x80, 0x3F}, "1.0f"},{0.0f, {0x00, 0x00, 0x00, 0x00}, "0.0f"},{-1.0f, {0x00, 0x00, 0x80, 0xBF}, "-1.0f"}};for (const auto& testCase : testCases) {std::cout << "测试: " << testCase.description << std::endl;// 十六进制转浮点数float result = hexToFloat(testCase.expectedHex);std::cout << "十六进制 → 浮点数: " << result << "f" << std::endl;// 浮点数转十六进制uint8_t hexData[4];floatToHex(testCase.value, hexData);bool hexMatch = true;for (int i = 0; i < 4; i++) {if (hexData[i] != testCase.expectedHex[i]) {hexMatch = false;break;}}std::cout << "十六进制匹配: " << (hexMatch ? "✓ 成功" : "✗ 失败") << std::endl;std::cout << "----------------------------------------" << std::endl;}
}int main() {// 基本功能演示std::cout << "========== 基本功能演示 ==========" << std::endl;float original = 3.0f;std::cout << "原始浮点数: " << original << "f" << std::endl;// 浮点数转十六进制uint8_t hexData[4];floatToHex(original, hexData);printHexData(hexData, "转换后的十六进制:");// 十六进制转回浮点数float converted = hexToFloat(hexData);std::cout << "转换回浮点数: " << converted << "f" << std::endl;// 使用vector版本的函数auto hexVector = floatToHex(original);std::cout << "Vector版本结果: ";for (uint8_t byte : hexVector) {std::cout << "0x" << std::hex << static_cast<int>(byte) << " ";}std::cout << std::dec << std::endl;// 运行完整测试testConversions();testSpecificCases();return 0;
}