在 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,要么将指向浮点值的指针转换为指向相同大小的无符号整数的指针,并以十六进制格式打印间接值。这两种方法都有助于以高效的方式对浮点进行位级分析。