在 C++ 中显示浮点类型的二进制表示
Showing binary representation of floating point types in C++
考虑以下整数类型代码:
template <class T>
std::string as_binary_string( T value ) {
return std::bitset<sizeof( T ) * 8>( value ).to_string();
}
int main() {
unsigned char a(2);
char b(4);
unsigned short c(2);
short d(4);
unsigned int e(2);
int f(4);
unsigned long long g(2);
long long h(4);
std::cout << "a = " << +a << " " << as_binary_string( a ) << std::endl;
std::cout << "b = " << +b << " " << as_binary_string( b ) << std::endl;
std::cout << "c = " << c << " " << as_binary_string( c ) << std::endl;
std::cout << "d = " << c << " " << as_binary_string( d ) << std::endl;
std::cout << "e = " << e << " " << as_binary_string( e ) << std::endl;
std::cout << "f = " << f << " " << as_binary_string( f ) << std::endl;
std::cout << "g = " << g << " " << as_binary_string( g ) << std::endl;
std::cout << "h = " << h << " " << as_binary_string( h ) << std::endl;
std::cout << "\nPress any key and enter to quit.\n";
char q;
std::cin >> q;
return 0;
}
非常简单明了,效果很好,而且非常简单。
编辑
如何编写一个函数来在编译时提取任意浮点类型的二进制或位模式?
关于浮点数,据我所知,在任何现有库中我都没有发现任何类似的东西。我已经搜索 google 几天来寻找一个,所以我尝试编写自己的函数但没有成功。自从我最初问过这个问题以来,我不再有可用的尝试代码,所以我无法准确地向您展示所有不同的实现尝试及其编译器 - 构建错误。我有兴趣尝试在编译期间以通用方式为浮点数生成位模式,并希望将其集成到我现有的 class 中,它可以无缝地对任何整数类型执行相同的操作。至于浮动类型本身,我已经考虑了不同的格式和体系结构字节序。对于我的一般用途,浮点类型的标准 IEEE 版本是我需要关心的全部内容。
当我最初问这个问题时,iBug 建议我编写自己的函数,而我正在尝试这样做。我理解二进制数、内存大小和数学,但是当我试图将它们与浮点类型如何存储在内存中及其不同部分 {sign bit, base & exp } 放在一起时,我遇到了最大的麻烦。
从那时起,根据那些给出了很好答案的人的建议 - 例如,我能够编写一个非常适合我现有的 class 模板的函数,现在它可以用于我的预期目的。
自己写一个怎么样?
static_assert(sizeof(float) == sizeof(uint32_t));
static_assert(sizeof(double) == sizeof(uint64_t));
std::string as_binary_string( float value ) {
std::uint32_t t;
std::memcpy(&t, &value, sizeof(value));
return std::bitset<sizeof(float) * 8>(t).to_string();
}
std::string as_binary_string( double value ) {
std::uint64_t t;
std::memcpy(&t, &value, sizeof(value));
return std::bitset<sizeof(double) * 8>(t).to_string();
}
如果浮点数的大小不同,您可能需要更改辅助变量 t
。
您也可以逐位复制它们。这速度较慢,但适用于任意类型。
template <typename T>
std::string as_binary_string( T value )
{
const std::size_t nbytes = sizeof(T), nbits = nbytes * CHAR_BIT;
std::bitset<nbits> b;
std::uint8_t buf[nbytes];
std::memcpy(buf, &value, nbytes);
for(int i = 0; i < nbytes; ++i)
{
std::uint8_t cur = buf[i];
int offset = i * CHAR_BIT;
for(int bit = 0; bit < CHAR_BIT; ++bit)
{
b[offset] = cur & 1;
++offset; // Move to next bit in b
cur >>= 1; // Move to next bit in array
}
}
return b.to_string();
}
你说不需要标准。所以,这是在我的电脑上用 clang 工作的:
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
char *result;
result=new char[33];
fill(result,result+32,'0');
float input;
cin >>input;
asm(
"mov %0,%%eax\n"
"mov %1,%%rbx\n"
".intel_syntax\n"
"mov rcx,20h\n"
"loop_begin:\n"
"shr eax\n"
"jnc loop_end\n"
"inc byte ptr [rbx+rcx-1]\n"
"loop_end:\n"
"loop loop_begin\n"
".att_syntax\n"
:
: "m" (input), "m" (result)
);
cout <<result <<endl;
delete[] result;
return 0;
}
此代码对计算机体系结构做出了一系列假设,我不确定它可以在多少台计算机上运行。
编辑:
我的电脑是 64 位 Mac-Air。这个程序基本上是通过分配一个 33 字节的字符串并用 '0'
填充前 32 个字节来工作的(第 33 个字节将自动为 '[=12=]'
)。
然后它使用内联汇编将 float 存储到一个 32 位寄存器中,然后重复将其右移一位。
如果寄存器中的最后一位在移位前为 1,则将其存储到进位标志中。
然后汇编代码检查进位标志,如果它包含 1,则将字符串中的相应字节增加 1。
由于之前初始化为'0'
,所以会变为'1'
。
因此,实际上,当程序集中的循环完成时,float 的二进制表示被存储到字符串中。
此代码仅适用于 x64(它使用 64 位寄存器 "rbx"
和 "rcx"
来存储循环的指针和计数器),但我认为很容易将其调整为在其他处理器上工作。
一个 IEEE 浮点数如下所示
sign exponent mantissa
1 bit 11 bits 52 bits
注意尾数前隐藏1,指数
有偏差,所以 1023 = 0,而不是二进制补码。
通过 memcpy()ing 到 64 位无符号整数,然后您可以应用 AND 和
OR 掩码以获得位模式。安排可能是大端
或小端。
您可以通过简单的数字轻松计算出您的安排
例如 1 或 2。
您可以通过将 float/double 的地址转换为 char 并以这种方式迭代它来滚动:
#include <memory>
#include <iostream>
#include <limits>
#include <iomanip>
template <typename T>
std::string getBits(T t) {
std::string returnString{""};
char *base{reinterpret_cast<char *>(std::addressof(t))};
char *tail{base + sizeof(t) - 1};
do {
for (int bits = std::numeric_limits<unsigned char>::digits - 1; bits >= 0; bits--) {
returnString += ( ((*tail) & (1 << bits)) ? '1' : '0');
}
} while (--tail >= base);
return returnString;
}
int main() {
float f{10.0};
double d{100.0};
double nd{-100.0};
std::cout << std::setprecision(1);
std::cout << getBits(f) << std::endl;
std::cout << getBits(d) << std::endl;
std::cout << getBits(nd) << std::endl;
}
我机器上的输出(注意第三个输出中的符号翻转):
01000001001000000000000000000000
0100000001011001000000000000000000000000000000000000000000000000
1100000001011001000000000000000000000000000000000000000000000000
通常人们要么使用std::hexfloat
,要么将指向浮点值的指针转换为指向相同大小的无符号整数的指针,并以十六进制格式打印间接值。这两种方法都有助于以高效的方式对浮点进行位级分析。
考虑以下整数类型代码:
template <class T>
std::string as_binary_string( T value ) {
return std::bitset<sizeof( T ) * 8>( value ).to_string();
}
int main() {
unsigned char a(2);
char b(4);
unsigned short c(2);
short d(4);
unsigned int e(2);
int f(4);
unsigned long long g(2);
long long h(4);
std::cout << "a = " << +a << " " << as_binary_string( a ) << std::endl;
std::cout << "b = " << +b << " " << as_binary_string( b ) << std::endl;
std::cout << "c = " << c << " " << as_binary_string( c ) << std::endl;
std::cout << "d = " << c << " " << as_binary_string( d ) << std::endl;
std::cout << "e = " << e << " " << as_binary_string( e ) << std::endl;
std::cout << "f = " << f << " " << as_binary_string( f ) << std::endl;
std::cout << "g = " << g << " " << as_binary_string( g ) << std::endl;
std::cout << "h = " << h << " " << as_binary_string( h ) << std::endl;
std::cout << "\nPress any key and enter to quit.\n";
char q;
std::cin >> q;
return 0;
}
非常简单明了,效果很好,而且非常简单。
编辑
如何编写一个函数来在编译时提取任意浮点类型的二进制或位模式?
关于浮点数,据我所知,在任何现有库中我都没有发现任何类似的东西。我已经搜索 google 几天来寻找一个,所以我尝试编写自己的函数但没有成功。自从我最初问过这个问题以来,我不再有可用的尝试代码,所以我无法准确地向您展示所有不同的实现尝试及其编译器 - 构建错误。我有兴趣尝试在编译期间以通用方式为浮点数生成位模式,并希望将其集成到我现有的 class 中,它可以无缝地对任何整数类型执行相同的操作。至于浮动类型本身,我已经考虑了不同的格式和体系结构字节序。对于我的一般用途,浮点类型的标准 IEEE 版本是我需要关心的全部内容。
当我最初问这个问题时,iBug 建议我编写自己的函数,而我正在尝试这样做。我理解二进制数、内存大小和数学,但是当我试图将它们与浮点类型如何存储在内存中及其不同部分 {sign bit, base & exp } 放在一起时,我遇到了最大的麻烦。
从那时起,根据那些给出了很好答案的人的建议 - 例如,我能够编写一个非常适合我现有的 class 模板的函数,现在它可以用于我的预期目的。
自己写一个怎么样?
static_assert(sizeof(float) == sizeof(uint32_t));
static_assert(sizeof(double) == sizeof(uint64_t));
std::string as_binary_string( float value ) {
std::uint32_t t;
std::memcpy(&t, &value, sizeof(value));
return std::bitset<sizeof(float) * 8>(t).to_string();
}
std::string as_binary_string( double value ) {
std::uint64_t t;
std::memcpy(&t, &value, sizeof(value));
return std::bitset<sizeof(double) * 8>(t).to_string();
}
如果浮点数的大小不同,您可能需要更改辅助变量 t
。
您也可以逐位复制它们。这速度较慢,但适用于任意类型。
template <typename T>
std::string as_binary_string( T value )
{
const std::size_t nbytes = sizeof(T), nbits = nbytes * CHAR_BIT;
std::bitset<nbits> b;
std::uint8_t buf[nbytes];
std::memcpy(buf, &value, nbytes);
for(int i = 0; i < nbytes; ++i)
{
std::uint8_t cur = buf[i];
int offset = i * CHAR_BIT;
for(int bit = 0; bit < CHAR_BIT; ++bit)
{
b[offset] = cur & 1;
++offset; // Move to next bit in b
cur >>= 1; // Move to next bit in array
}
}
return b.to_string();
}
你说不需要标准。所以,这是在我的电脑上用 clang 工作的:
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
char *result;
result=new char[33];
fill(result,result+32,'0');
float input;
cin >>input;
asm(
"mov %0,%%eax\n"
"mov %1,%%rbx\n"
".intel_syntax\n"
"mov rcx,20h\n"
"loop_begin:\n"
"shr eax\n"
"jnc loop_end\n"
"inc byte ptr [rbx+rcx-1]\n"
"loop_end:\n"
"loop loop_begin\n"
".att_syntax\n"
:
: "m" (input), "m" (result)
);
cout <<result <<endl;
delete[] result;
return 0;
}
此代码对计算机体系结构做出了一系列假设,我不确定它可以在多少台计算机上运行。
编辑:
我的电脑是 64 位 Mac-Air。这个程序基本上是通过分配一个 33 字节的字符串并用 '0'
填充前 32 个字节来工作的(第 33 个字节将自动为 '[=12=]'
)。
然后它使用内联汇编将 float 存储到一个 32 位寄存器中,然后重复将其右移一位。
如果寄存器中的最后一位在移位前为 1,则将其存储到进位标志中。
然后汇编代码检查进位标志,如果它包含 1,则将字符串中的相应字节增加 1。
由于之前初始化为'0'
,所以会变为'1'
。
因此,实际上,当程序集中的循环完成时,float 的二进制表示被存储到字符串中。
此代码仅适用于 x64(它使用 64 位寄存器 "rbx"
和 "rcx"
来存储循环的指针和计数器),但我认为很容易将其调整为在其他处理器上工作。
一个 IEEE 浮点数如下所示
sign exponent mantissa
1 bit 11 bits 52 bits
注意尾数前隐藏1,指数 有偏差,所以 1023 = 0,而不是二进制补码。 通过 memcpy()ing 到 64 位无符号整数,然后您可以应用 AND 和 OR 掩码以获得位模式。安排可能是大端 或小端。 您可以通过简单的数字轻松计算出您的安排 例如 1 或 2。
您可以通过将 float/double 的地址转换为 char 并以这种方式迭代它来滚动:
#include <memory>
#include <iostream>
#include <limits>
#include <iomanip>
template <typename T>
std::string getBits(T t) {
std::string returnString{""};
char *base{reinterpret_cast<char *>(std::addressof(t))};
char *tail{base + sizeof(t) - 1};
do {
for (int bits = std::numeric_limits<unsigned char>::digits - 1; bits >= 0; bits--) {
returnString += ( ((*tail) & (1 << bits)) ? '1' : '0');
}
} while (--tail >= base);
return returnString;
}
int main() {
float f{10.0};
double d{100.0};
double nd{-100.0};
std::cout << std::setprecision(1);
std::cout << getBits(f) << std::endl;
std::cout << getBits(d) << std::endl;
std::cout << getBits(nd) << std::endl;
}
我机器上的输出(注意第三个输出中的符号翻转):
01000001001000000000000000000000
0100000001011001000000000000000000000000000000000000000000000000
1100000001011001000000000000000000000000000000000000000000000000
通常人们要么使用std::hexfloat
,要么将指向浮点值的指针转换为指向相同大小的无符号整数的指针,并以十六进制格式打印间接值。这两种方法都有助于以高效的方式对浮点进行位级分析。