在 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 型是四舍五入吗?