6

我想将 a 转换float为 a unsigned long,同时保留 the 的二进制表示float(所以我不想转换5.05!)。

这很容易通过以下方式完成:

float f = 2.0;
unsigned long x = *((unsigned long*)&f)

但是,现在我需要在 a 中做同样的事情#define,因为我想稍后在一些数组初始化中使用它(所以 [inline] 函数不是一个选项)。

这不会编译:

#define f2u(f) *((unsigned long*)&f)

如果我这样称呼它:

unsigned long x[] = { f2u(1.0), f2u(2.0), f2u(3.0), ... }

我得到的错误是(逻辑上):

lvalue required as unary ‘&’ operand

注意:下面建议的一种解决方案是union为我的数组使用一种类型。但是,这是没有选择的。我实际上正在执行以下操作:

#define Calc(x) (((x & 0x7F800000) >> 23) - 127)
unsigned long x[] = { Calc(f2u(1.0)), Calc(f2u(2.0)), Calc(f2u(3.0)), ... }

所以数组真的将/必须是 type long[]

4

8 回答 8

2

您可能应该使用联合:

union floatpun {
    float f;
    unsigned long l;
};

union floatpun x[3] = { {1.0}, {2.0}, {3.0} };

也许:

union {
    float f[3];
    unsigned long l[3];
} x = { { 1.0, 2.0, 3.0 } };

(后者将让您通过x.l需要类型数组的地方unsigned long [3])。

当然,您需要确保在您的平台上具有相同的大小unsigned longfloat

于 2011-08-23T07:09:24.750 回答
1

按照@caf的回答,您可以使用union

#define F2L(x) ((union{float f;unsigned long l;})(x)).l

int main(int argc, char *argv[])
{
    unsigned long array[] = {F2L(1.0f),F2L(2.0f),F2L(3.0f)};
    printf("%x %x %x\n",array[0],array[1],array[2]);
    printf("%x\n",array[1] - array[0]);  
  system("PAUSE");  
  return 0;
}

这打印(在 GCC 3.4.5 下,我知道旧的 :(,但这就是我在 atm 的所有内容,使用 -O3):

3f800000 40000000 40400000
800000

并且生成的 asm 确认将它们视为unsigned longs:

CPU Disasm
Address   Hex dump          Command                                  Comments
004012A8  |.  C745 E8 00008 MOV DWORD PTR SS:[LOCAL.6],3F800000
004012AF  |.  B9 0000803F   MOV ECX,3F800000
004012B4  |.  BA 00004040   MOV EDX,40400000
004012B9  |.  894C24 04     MOV DWORD PTR SS:[LOCAL.13],ECX          ; /<%x> => 3F800000
004012BD  |.  B8 00000040   MOV EAX,40000000                         ; |
004012C2  |.  895424 0C     MOV DWORD PTR SS:[LOCAL.11],EDX          ; |<%x> => 40400000
004012C6  |.  C745 EC 00000 MOV DWORD PTR SS:[LOCAL.5],40000000      ; |
004012CD  |.  C745 F0 00004 MOV DWORD PTR SS:[LOCAL.4],40400000      ; |
004012D4  |.  894424 08     MOV DWORD PTR SS:[LOCAL.12],EAX          ; |<%x> => 40000000
004012D8  |.  C70424 003040 MOV DWORD PTR SS:[LOCAL.14],OFFSET 00403 ; |format => "%x %x %x
"
004012DF  |.  E8 6C050000   CALL <JMP.&msvcrt.printf>                ; \MSVCRT.printf
004012E4  |.  C70424 0A3040 MOV DWORD PTR SS:[LOCAL.14],OFFSET 00403 ; /format => "%x
"
004012EB  |.  8B55 E8       MOV EDX,DWORD PTR SS:[LOCAL.6]           ; |
004012EE  |.  8B45 EC       MOV EAX,DWORD PTR SS:[LOCAL.5]           ; |
004012F1  |.  29D0          SUB EAX,EDX                              ; |
004012F3  |.  894424 04     MOV DWORD PTR SS:[LOCAL.13],EAX          ; |<%x> => 800000
004012F7  |.  E8 54050000   CALL <JMP.&msvcrt.printf>                ; \MSVCRT.printf
于 2011-08-23T08:08:30.697 回答
1

lvalue表示可以分配的东西。 1.0是一个常数,而不是一个变量,你不能得到它的引用(既不分配给它)。

意义:

这个:

unsigned long x[3] = { f2u(1.0), f2u(2.0), f2u(3.0) }

实际上是:

unsigned long x[3] = { *((unsigned long*)&1.0, *((unsigned long*)&2.0, *((unsigned long*)&3.0 }

1.02.0并且3.0没有地址。

问题与定义无关,#define因为定义是一个简单的替换,此代码也无效:

unsigned long x = *((unsigned long*)&1.0;

问题是您试图引用没有地址的立即值。

于 2011-08-23T06:58:49.590 回答
1

注意:下面建议的一种解决方案是对我的数组使用联合类型。但是,这是没有选择的。我实际上正在执行以下操作

#define Calc(x) (((x & 0x7F800000) >> 23) - 127)
unsigned long x[] = { Calc(f2u(1.0)), Calc(f2u(2.0)), Calc(f2u(3.0)), ... }

所以数组真的将/必须是 long[] 类型。

在这种情况下,您可能无法省略中间的步骤。

unsigned float x[] = { 1.0, 2.0, 3.0, ...};
unsigned int y[sizeof x/sizeof x[0]];
for (i=0; i<sizeof x/sizeof x[0]; i++) {
    y[i] = Calc(f2u(x[i]));
}

我承认它不是很优雅。但是,如果您因此遇到内存困难(嵌入式系统?),您可以单独执行此操作并自动创建具有正确数组的源文件。


编辑:

另一种解决方案是告诉编译器你真正想要什么。显然,您想要计算浮点数的指数。所以你可以做

#define expo(f) ((long)(log((f)) / log(2)))

这似乎正是你打算做的事情。在我看来,一个signed charwoud 就足够了,如果没有,一个int16_t.

于 2011-08-23T07:39:11.283 回答
0

为什么不简单地自己对数据运行一个 init 函数。您可以在运行时而不是编译时使用计算更新无符号长表。

#include <stdio.h>

#define Calc(x) (((x & 0x7F800000) >> 23) - 127)

float f[] = {1.0, 2.0, 3.0, 5.0, 250.0, 300.5};
unsigned long *l = (unsigned long *)f;

int main(int argc, const char *argv[])
{
    int i;

    for (i = 0; i < sizeof(f) / sizeof(f[0]); i++)
    {
        printf("[%d] %f %p", i, f[i], l[i]);
        l[i] = Calc(l[i]);
        printf(" | %f %p\n", f[i], l[i]);
    }

    return 0;
}

样本输出:

Andreas Stenius@Neo /opt
$ gcc float2long.c && ./a.exe
[0] 1.000000 0x3f800000 | 0.000000 0x0
[1] 2.000000 0x40000000 | 0.000000 0x1
[2] 3.000000 0x40400000 | 0.000000 0x1
[3] 5.000000 0x40a00000 | 0.000000 0x2
[4] 250.000000 0x437a0000 | 0.000000 0x7
[5] 300.500000 0x43964000 | 0.000000 0x8

Andreas Stenius@Neo /opt
$
于 2011-08-23T07:47:32.497 回答
0

如果您只需要这个数组,那么另一种方法可能是

float x[3] = { 1.0, 2.0, 3.0 };
unsigned long * y = (unsigned long*)&x;
于 2011-08-23T07:11:23.967 回答
-1

您尝试使用的方法在形式上是非法的。基于指针的原始内存重新解释构成所谓的“类型双关语”,一些编译器会检测到并警告您。在一般情况下,类型双关会导致 C 中的未定义行为。这根本不是理论上的未定义行为,因为一些编译器依赖于此进行优化(例如,参见GCC 中的严格值语义)。

另一种类型的双关语是使用联合来重新解释原始内存数据。以这种方式使用联合在形式上与普通的基于指针的类型双关语一样非法(即导致未定义的行为),即使某些编译器公开允许这样做。(更新:C99 的 TC3 正式允许使用联合。)

将一种类型的对象作为另一种(不相关)类型的对象进行检查的最安全和合法的方法是使用memcpy. 只需将源对象复制到目标对象并使用/检查“副本”而不是原始对象

float f = 2.0;
unsigned long x;

assert(sizeof f == sizeof x); /* STATIC_ASSERT */
memcpy(&x, &f, sizeof x);

当然,这并不是您在应用程序中所需要的,因为您正在寻找可以重新解释聚合初始化程序中的常量的东西。但是,您必须记住,首先,这种重新解释(以所有形式)仅适用于左值而不适用于直接常量。其次,C89/90 中的所有聚合初始化器(C99 中静态对象的聚合初始化器)都必须是常量,而重新解释不会产生常量。

于 2011-08-23T07:15:42.593 回答
-1

这里的问题是您正在尝试获取常量的地址。不幸的是,常量不是左值,它们没有地址可取。

据我所知,没有办法使用宏来做到这一点。另外,如果我没记错的话,C 标准不保证 along和 afloat将使用相同的位数,因此即使您的原始方法在不同的架构上也可能不可靠。

于 2011-08-23T07:01:53.847 回答