1

目前我需要忽略可移植性问题,并假设 long double 数据类型在每个架构上都是一致的 16 字节值,并且始终以 IEEE 754 四精度二进制浮点格式存储在内存中。这意味着最左边的位是符号位,后跟 15 位指数和另外 112 位二进制数据,以表示有效的十进制数字。当然不是十进制或 BCD,这都是二进制。

因此,数字 3 在内存中的表示如下所示:

(dbx) x &ld3 / 1 E
0xffffffff7ffff400: 0x40008000 0x00000000 0x00000000 0x00000000

由于 C 中的这一行,我从调试器中得到了它:

long double ld3 = 3.0;

在这里,我们看到前 16 位,即最左边的 16 位,是:

0x4000h = 0100 0000 0000 0000b 

这里符号位为零,因此根据 IEEE754 规则表示正值,然后我们有 15 位的指数值 100 0000 0000 0000b。我花了一段时间阅读然后重新阅读以下文档:

http://en.wikipedia.org/wiki/Quadruple_precision

该指数按面值计算为 2^14,即 16,384。然而,(2^14) - 1 的“零偏移”值是 16,383,因此我上面的指数实际上只是 16,384 - 16,383 = 1。到目前为止好东西。

实际数字区域中的数据,接下来的 112 位是 0x8000 0000 ... 0000h 看起来是 1000 0000 ... 0000 似乎是错误的。三的二进制值应该是两位一,后跟一堆零。所以这让我很困扰。

所以我想编写一个代码位,它将一个长双精度变量打印为十六进制字节序列。但是,我遇到了实际获取这些字节作为变量地址的偏移量的问题。

我试试这个:

uint8_t j = 0;
j = ( (uint8_t *)(&ld3) + 2 );
fprintf ( stdout , "         &ld3 = %p\n", &ld3 );
fprintf ( stdout , "byte 2 of ld3 = 0x%02x\n", j );

我看到这个:

         &ld3 = ffffffff7ffff4b0
byte 2 of ld3 = 0xb2

我不知道它来自哪里,但它不能来自变量 ld3 占用的内存区域。

所以我希望有一个 for 循环遍历 long double 的 16 个字节,并在每个字节处打印出十六进制值,但我认为我在转换指针或地址或其他东西时遇到了严重错误。

所以我想问题是......在 long double 变量中获取特定字节的神奇 foo 是什么?


跟进:有了下面提供的建议,我可以展示这个工作案例,我们在最后两位看到一点噪音或错误。

#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    long double two_ld = 2.0;
    long double e64_ld;
    uint8_t byt = 0, k;

    e64_ld = expl(logl(two_ld)*(long double)64.0);

    fprintf ( stdout , " sizeof (_ld) = %i\n", sizeof(e64_ld) );
    fprintf ( stdout , "   ( 2 ^ 64 ) = %36.32Le\n", e64_ld );

    fprintf ( stdout , "              = 0x" );
    for ( k = 0; k<16; k++ )
        fprintf ( stdout , "%02x ", * ( (uint8_t *)(&e64_ld) + k ) );
    fprintf ( stdout , "\n" );

    return ( EXIT_SUCCESS );

}

输出显示最右侧的位具有确实不应该存在的值:

 sizeof (_ld) = 16
   ( 2 ^ 64 ) = 1.84467440737095516160000000000000e+19
              = 0x40 3f 00 00 00 00 00 00 00 00 00 00 00 00 00 02 

不过,考虑到我使用对数计算了 2^64,这还不错。

4

2 回答 2

2

您缺少取消引用,因此正在尝试将指针分配给 char。这就是为什么您会看到b2(地址的 LSB = b0+ 2= b2)。

改变:

j = ( (uint8_t *)(&ld3) + 2 );

至:

j = * ( (uint8_t *)(&ld3) + 2 );
   ^^^
   missing
   dereference

或者考虑更容易阅读的形式:

j = ((uint8_t *)&ld3)[2];

另请注意,如果您启用了编译器警告(例如gcc -Wall ...),那么编译器会通知您该问题。始终在启用警告的情况下编译并注意任何警告。

于 2013-07-28T20:47:05.203 回答
0

你的假设是完全错误的。我在流行的计算机上遇到了三种不同的 long double 表示:

long double = 64 位 IEEE 格式(ARM 处理器、PowerPC、带有某些编译器的 x86)

long double = 80 位 IEEE 格式加上 48 个未使用的位(x86 与不同的编译器)

long double = 两个 64 位 IEEE 数字(带有某些编译器选项的 PowerPC)。这里的长双精度数是一对具有圆形 (x + y) = x 属性的双精度数 (x, y)。

于 2015-05-31T09:10:56.360 回答