【问题标题】:What is the correct way to get the binary representation of long double? [duplicate]获取 long double 的二进制表示的正确方法是什么? [复制]
【发布时间】:2021-07-15 19:35:01
【问题描述】:

这是我的尝试:

#include <iostream>

union newType {
        long double firstPart;
        unsigned char secondPart[sizeof(firstPart)];
} lDouble;

int main() {
    lDouble.firstPart = -16.5;

    for (int_fast16_t i { sizeof(lDouble) - 1 }; i >= 0; --i)
        std::cout << (int)lDouble.secondPart[i] << " ";

    return 0;
}
输出:0 0 0 0 0 0 192 3 132 0 0 0 0 0 0 0
十六进制:0 0 0 0 0 0 c0 3 84 0 0 0 0 0 0 0

我几乎同意“c0 3 84”部分,即“1100 0000 0000 0011 1000 0100”。

-16.5 = -1.03125 * 2^4 = (-1 + (-0.5) * 2^-4) * 2^4 因此,我的小数部分的第 117 位必须为 1,在第 5 次除法之后,我将只得到“0”。 符号(-):1 指数(2^4):4 + 16383 = 16387 = 100 0000 0000 0011 分数:0000 1000 和 104 '0' 结果:1| 100 0000 0000 0011| 0000 1000 和 104 '0' 十六进制:c 0 0 3 0 8 和 26 '0' 或:c0 3 8 0 0 0 0 0 0 0 0 0 0 0 0 0

我没有得到两件事:

  1. "c0 3 84" - 我的计算在哪里丢失了 4?我的猜测是它以某种方式存储 1(113 位)并且不应该存储。然后是 1000 0100 而不是 0000 1000(在“c0 3”之后),这正是“84”。但我们总是存储 112 位,而 1 总是隐含的。
  2. 为什么我的输出不是从 192 开始?为什么从0开始?我认为第一位是符号位,然后是指数(15 位)和小数(112 位)。

我已经设法表示其他数据类型(double、float、unsigned char 等)。使用 double 我采用了类似的方法并得到了预期的结果(例如 double -16.5 输出 192 48 128 0 0 0 0 0 或 c0 30 80 0 0 0 0 0)。

当然我已经测试过How to print binary representation of a long double as in computer memory?的解决方案

我的 -16.5 的值为:0 0 0 0 0 0 0 0x84 0x3 0xc0 0xe2 0x71 0xf 0x56 0 0
如果我恢复这个我得到: 0 0 56 f 71 e2 c0 3 84 0 0 0 0 0 0 0

我不明白为什么(再次)序列不是从符号位开始,那些“56 f 71 e2 c0”是什么?他们来自哪里?为什么(再次)在“8”之后有“4”?

【问题讨论】:

标签: c++ ieee-754 long-double


【解决方案1】:

获取 long double 二进制表示的正确方法是什么?

与获取任何普通类型的二进制表示的方式相同。重新解释为unsigned char 的数组,并迭代每个字节是典型且定义明确的解决方案。

std::bitset 有助于二进制表示:

long double ld = -16.5;
unsigned char* it = reinterpret_cast<unsigned char*>(&ld);
for (std::size_t i = 0; i < sizeof(ld); i++) {
    std::cout
        << "byte "
        << i
        << '\t'
        << std::bitset<CHAR_BIT>(it[i])
        << '\t'
        << std::hex << int(it[i])
        << '\t'
        << std::dec << int(it[i])
        << '\n';
}

某些系统上的示例输出:

byte 0  00000000    0   0
byte 1  00000000    0   0
byte 2  00000000    0   0
byte 3  00000000    0   0
byte 4  00000000    0   0
byte 5  00000000    0   0
byte 6  00000000    0   0
byte 7  10000100    84  132
byte 8  00000011    3   3
byte 9  11000000    c0  192
byte 10 01000000    40  64
byte 11 00000000    0   0
byte 12 00000000    0   0
byte 13 00000000    0   0
byte 14 00000000    0   0
byte 15 00000000    0   0

请注意,由于读取联合的非活动成员,您的示例在 C++ 中具有未定义的行为。


为什么我的输出不是从 192 开始的?

可能是因为末尾的那些字节恰好是填充。

为什么从0开始?

因为填充包含垃圾。

我认为第一位是符号位,然后是指数(15 位)和小数(112 位)。

与其说是“第一位”位,不如说是“最重要”位,不包括填充。显然,您错误地假设了位数,因为其中一些用于填充。

请注意,C++ 不保证浮点表示是 IEEE-754,事实上,long double 通常不是 128 位“四倍”精度浮点数,而是 80 位“扩展”精度浮点数。例如在 x86 CPU 架构系列中就是这种情况。

【讨论】:

  • 为什么在您的示例中我们也是从 0 开始,而不是从 40 或 c0(或 84)开始。是因为那些“填充字节”吗?并且为此使用了多少字节?它是否取决于我的系统/编译器?
  • @Xofrio 所有浮点类型的格式取决于系统。填充只是未使用的内存。它可以随时具有任何值,并且这些值对对象的值没有影响。
  • 所以你的例子中的“byte 10”存储了随机值,对吗?
  • @Xofrio 在 80 位精度下,在具有 8 位字节的 CPU 上,前 10 个字节,即 0...9 具有有意义的值,其余 10...15 是填充。
  • “重新解释为 unsigned char 的数组”的措辞不清楚。在 C 中,存储在 long double 成员中并从作为 unsigned char 数组的成员中读取会重新解释字节,因此人们可能会认为该措辞意味着这一点。在 C++ 中,这是未定义的。最好明确说明定义的内容。
猜你喜欢
  • 2016-11-01
  • 2011-04-04
  • 2012-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多