将float转换为unsigned long以访问c #define中的float内部

时间:2011-08-23 06:55:58

标签: c floating-point floating-point-conversion

我想将float转换为unsigned long,同时保留float的二进制表示形式(所以我 想要转换{{} 1}}到5.0!)。

这很容易通过以下方式进行:

5

但是,现在我需要在float f = 2.0; unsigned long x = *((unsigned long*)&f) 中执行相同的操作,因为我希望稍后在某些数组初始化中使用它(因此[inline]函数不是一个选项)。

这不编译:

#define

如果我这样称呼它:

#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)), ... } 类型。

8 个答案:

答案 0 :(得分: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在您的平台上具有相同的尺寸。

答案 1 :(得分: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.03.0没有地址。

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

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

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

答案 2 :(得分: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 char就足够了,如果没有,int16_t

答案 3 :(得分:0)

如果它只是你需要的那个数组,那么另一种方法可能是

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

答案 4 :(得分:0)

为什么不自己简单地对数据运行init函数。您可以在运行时使用计算更新unsigned long表,而不是编译时。

#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
$

答案 5 :(得分:0)

跟着@​​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,我知道:(,但这就是我所拥有的所有地方,使用-O3):

3f800000 40000000 40400000
800000

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

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

答案 6 :(得分:-1)

这里的问题是你试图获取常量的地址。不幸的是,常量不是左值,而且它们没有地址可以使用。

据我所知,使用宏无法做到这一点。另外,如果我没记错的话,C标准并不保证longfloat将使用相同的位数,因此即使您的原始方法在不同的体系结构上也可能不可靠。

答案 7 :(得分:-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中静态对象的聚合初始化器)都需要是常量,而重新解释不会产生常量。