C++中适配NIST-800-22的double类型转二进制方法咨询
我需要使用NIST-800-22测试套件对C++中的各类伪随机数生成器(PRNG)进行测试,该套件要求将生成的数值转换为二进制格式存储。整数类型的转换难度不大,但仅生成double类型数值的生成器带来了难题。我了解double类型遵循IEEE 754浮点标准存储,但不确定如何将其转换为二进制。ChatGPT提供的转换方案生成的二进制串(如0011111111100100001010110110010110100000000000000000000000000000)看起来缺乏随机性,且我认为这会对测试数据造成过多干扰。请问是否有读取原始生成二进制数据的方法,或该如何获取符合要求的二进制数据?
ChatGPT提供的代码如下:
std::string doubleToBinary(double num) { uint64_t rawBits; std::memcpy(&rawBits, &num, sizeof(double)); std::bitset<64> binary(rawBits); return binary.to_string(); }
问题根源
ChatGPT的代码直接输出了double的全部64位原始二进制,但IEEE 754标准下的double类型结构为:
- 1位符号位
- 11位指数位
- 52位尾数位
如果你的PRNG输出的是**[0,1)区间的double值**(这是std::uniform_real_distribution等常见PRNG的输出范围),那么:
- 符号位固定为0(非负数)
- 指数位固定为
0x3FE(对应2^-1,因为[0,1)的数可表示为1.xxxx × 2^-1)
这就导致二进制串的前12位始终是001111111110,完全没有随机性,必然会干扰NIST-800-22的测试结果(比如频率测试会检测到0/1比例严重失衡)。
正确的二进制提取方式
1. 提取尾数位(推荐)
[0,1)区间的double值中,所有随机信息都存储在52位尾数位中。我们可以直接提取这部分位作为测试用的二进制数据:
#include <cstdint> #include <cstring> #include <bitset> #include <string> std::string doubleToTestBinary(double num) { // 确保num处于[0,1)区间,这是PRNG的典型输出 uint64_t rawBits; std::memcpy(&rawBits, &num, sizeof(double)); // 提取52位尾数:屏蔽高12位(符号位+指数位) const uint64_t mantissa_mask = (1ULL << 52) - 1; uint64_t mantissa = rawBits & mantissa_mask; // 转换为52位二进制串 std::bitset<52> binary(mantissa); return binary.to_string(); }
2. 直接写入原始字节流(需处理固定位)
如果需要直接将PRNG的输出以二进制文件形式提供给NIST套件,不要直接写入完整的double字节,而是只写入尾数的字节:
#include <fstream> #include <cstdint> #include <cstring> void writeDoublePRNGToBinary(std::ofstream& out, double num) { uint64_t rawBits; std::memcpy(&rawBits, &num, sizeof(double)); // 提取尾数的6字节(52位=6.5字节,实际取低6字节,最后4位在第7字节) // 注意字节序:根据NIST套件要求调整为大端或小端,此处以小端为例 uint8_t bytes[6]; std::memcpy(bytes, &rawBits, 6); out.write(reinterpret_cast<char*>(bytes), 6); // 处理剩余的4位(可选:如果需要连续无遗漏的位流) uint8_t last_byte = (rawBits >> 48) & 0x0F; out.write(reinterpret_cast<char*>(&last_byte), 1); }
3. 映射为整数再转换
将[0,1)的double值乘以2^52,取整数部分(即尾数的十进制表示),再转换为二进制:
#include <cstdint> #include <bitset> #include <string> std::string doubleToTestBinary(double num) { uint64_t mantissa = static_cast<uint64_t>(num * (1ULL << 52)); std::bitset<52> binary(mantissa); return binary.to_string(); }
注意事项
- 先确认PRNG的输出范围:如果输出不是[0,1),需要重新分析符号位和指数位的变化规律,避免引入非随机的固定位。
- NIST-800-22需要连续的二进制位流,因此可以将每个double提取的52位拼接成一个长串,或者按字节写入文件。
- 不要包含固定的符号位和指数位,这些非随机位会导致测试结果无效。
内容的提问来源于stack exchange,提问作者royalewski

