在 C99 中:如何访问 long double 变量中的特定字节

Posted

技术标签:

【中文标题】在 C99 中:如何访问 long double 变量中的特定字节【英文标题】:in C99 : how to access a specific byte within a long double variable 【发布时间】:2013-07-28 20:45:10 【问题描述】:

目前我需要忽略可移植性问题,并假设 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,这还不错。

【问题讨论】:

"3 的二进制值应该是两位 1 后跟一堆零。" - 您忽略了 IEEE-754 尾数具有隐含的前导“1”这一事实。 非常感谢您!我想知道那个丢失的部分在哪里。 【参考方案1】:

您缺少取消引用,因此正在尝试将指针分配给 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 ...),那么编译器会通知您该问题。始终在启用警告的情况下编译并注意任何警告。

【讨论】:

冒着说明显而易见的风险......这个带有取消引用指针的东西在我的脑海中只是简单的棘手。显然,我需要做更多的学习工作。 @paullanken:更糟糕的是,C 禁止通过指针转换进行类型双关语。提高优化级别,编译器可能不会做你认为它应该做的事情。最好使用联合,或者只使用 memcpy(编译器通常可以将具有较小常量大小的 memcpy 优化为仅移动指令,因此无需担心在这里进行函数调用效率低下)。【参考方案2】:

你的假设是完全错误的。我在流行的计算机上遇到了三种不同的 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)。

【讨论】:

以上是关于在 C99 中:如何访问 long double 变量中的特定字节的主要内容,如果未能解决你的问题,请参考以下文章

如何避免 Gson 将 JsonString 中的 int long 等数字转化为带小数的 Double

java中将一个double类型的数强制转换为long 型是四舍五入吗?

c语言long double 怎么输入,输出阿?

网易云课堂_C语言程序设计进阶_第一周:数据类型:整数类型浮点类型枚举类型

Java 中 long 和 double 的原子性?

请教c#中double类型转化为long类型的方法, 以及对double四舍五入的方法