Конвертировать float в unsigned long для доступа к внутренним объектам float, в c #define - PullRequest
5 голосов
/ 23 августа 2011

Я хочу преобразовать float в unsigned long, сохраняя при этом двоичное представление float (поэтому я не хочу привести 5.0 к 5!).

Это легко сделать следующим образом:

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

Однако теперь мне нужно сделать то же самое в #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)), ... }

Таким образом, массив действительно будет / должен иметь тип long[].

Ответы [ 8 ]

2 голосов
/ 23 августа 2011

Вы, вероятно, должны использовать союз:

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 long и float имеют одинаковый размер на вашей платформе.

1 голос
/ 23 августа 2011

Примечание. Одним из решений, которое было предложено ниже, было использование типа объединения для моего массива. Однако это не вариант. На самом деле я делаю следующее

#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]));
}

Признаюсь, это не очень элегантно. Но если у вас возникнут проблемы с памятью из-за этого (встроенная система?), Вы можете сделать это отдельно и автоматически создать исходный файл с правильным массивом.


EDIT:

Еще одним решением было бы сообщить компилятору, что вы действительно хотите. Очевидно, что вы хотите вычислить показатель числа с плавающей запятой. Так что вы могли бы просто сделать

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

Кажется, это именно то, что вы собираетесь делать. И мне кажется, что signed char достаточно, а если нет - int16_t.

1 голос
/ 23 августа 2011

lvalue означает что-то назначаемое. 1.0 является константой, а не переменной, и вы не можете получить ссылку на нее (ни назначить ей).

Значение:

Это:

* * 1010

На самом деле:

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

и 1.0, 2.0 и 3.0 не имеют адреса.

Проблема не связана с #define, так как define - это простая замена. Этот код также недействителен:

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

Проблема в том, что вы пытаетесь сослаться на непосредственные значения, которые не имеют адреса.

0 голосов
/ 23 августа 2011

следуя ответу @ 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
0 голосов
/ 23 августа 2011

Почему бы просто не запустить функцию 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
$
0 голосов
/ 23 августа 2011

Метод, который вы пытаетесь использовать, формально незаконен. Неинтерпретативная реинтерпретация памяти на основе указателей представляет собой так называемое «наказание типов», о котором некоторые компиляторы будут обнаруживать и предупреждать вас. Пуннинг типов в общем случае приводит к неопределенному поведению в C. И это вообще не теоретическое неопределенное поведение, поскольку некоторые компиляторы полагаются на это для оптимизации (см., Например, семантика строгих значений в GCC). 1003 *

Еще одно разнообразие типов штампов - это использование союзов для повторной интерпретации необработанных данных памяти. Такое использование союзов формально столь же незаконно (то есть приводит к неопределенному поведению), что и обычное наказание типов на основе указателей, даже если некоторые компиляторы открыто разрешают это. (Обновление: TC3 для C99 официально разрешил такое использование союзов.)

Наиболее безопасный и законный способ проверки объекта одного типа как объекта другого (не связанного) типа - использование memcpy. Просто скопируйте исходный объект в целевой объект и используйте / проверьте «копию» вместо оригинала

float f = 2.0;
unsigned long x;

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

Это, конечно, не совсем то, что вам нужно в вашем приложении, поскольку вы ищете что-то, что будет работать для реинтерпретации констант в агрегатном инициализаторе. Однако вы должны помнить, что, во-первых, этот вид реинтерпретации (во всех его формах) применим только к l-значениям, а не к непосредственным константам. И, во-вторых, все агрегатные инициализаторы в C89 / 90 (агрегатные инициализаторы для статических объектов в C99) должны быть константами, в то время как реинтерпретация не создает констант.

0 голосов
/ 23 августа 2011

Если вам нужен только этот массив, другой подход может быть

float x[3] = { 1.0, 2.0, 3.0 };
unsigned long * y = (unsigned long*)&x;
0 голосов
/ 23 августа 2011

Проблема в том, что вы пытаетесь взять адрес константы.К сожалению, константы не являются lvalues, и у них нет адреса для взятия.

Насколько я знаю, это невозможно сделать с помощью макроса.Кроме того, если я правильно помню, стандарт C не гарантирует, что long и float будут использовать одинаковое количество битов, поэтому даже ваш оригинальный метод может быть ненадежным на разных архитектурах.

...