Как выполнить побитовую операцию над числами с плавающей запятой

Я попробовал это:

float a = 1.4123;
a = a & (1 << 3);

Я получаю сообщение об ошибке компилятора о том, что операнд & не может быть типа с плавающей точкой.

Когда я делаю:

float a = 1.4123;
a = (int)a & (1 << 3);

Я запускаю программу. Единственное, что побитовая операция выполняется для целочисленного представления числа, полученного после округления.

Следующее также не допускается.

float a = 1.4123;
a = (void*)a & (1 << 3);

Я не понимаю почему int может быть приведен к void* но нет float,

Я делаю это, чтобы решить проблему, описанную в вопросе переполнения стека. Как решить линейные уравнения с использованием генетического алгоритма?,

11 ответов

Решение

На уровне языка не существует такой вещи, как "побитовая операция над числами с плавающей запятой". Побитовые операции в C/C++ работают со значением-представлением числа. И представление значений чисел с плавающей запятой не определено в C/C++. Числа с плавающей точкой не имеют битов на уровне представления значений, поэтому вы не можете применять к ним побитовые операции.

Все, что вы можете сделать, это проанализировать битовый контент необработанной памяти, занятой числом с плавающей запятой. Для этого вам нужно либо использовать объединение, как предложено ниже, или (эквивалентно и только в C++) интерпретировать объект с плавающей запятой как массив unsigned char объекты, как в

float f = 5;
unsigned char *c = reinterpret_cast<unsigned char *>(&f);
// inspect memory from c[0] to c[sizeof f - 1]

И, пожалуйста, не пытайтесь переосмыслить float объект как int объект, как предполагают другие ответы. Это не имеет особого смысла, это незаконно, и это не гарантирует работу в компиляторах, которые следуют строгим правилам псевдонимов при оптимизации. Единственный законный способ проверки содержимого памяти в C++ - это переосмысление его как массива [signed/unsigned] char,

Также обратите внимание, что вам технически не гарантируется, что представление с плавающей запятой в вашей системе является IEEE754 (хотя на практике это происходит, если вы явно не разрешаете этого не делать, и только в отношении -0.0, ±infinity и NaN).

Если вы пытаетесь изменить биты в представлении с плавающей точкой, вы можете сделать что-то вроде этого:

union fp_bit_twiddler {
    float f;
    int i;
} q;
q.f = a;
q.i &= (1 << 3);
a = q.f;

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

Вы можете обойти правило строгого псевдонима и выполнять побитовые операции сfloat набранный как uint32_t (если ваша реализация определяет это, что делает большинство) без неопределенного поведения с помощью memcpy():

float a = 1.4123f;
uint32_t b;

std::memcpy(&b, &a, 4);
// perform bitwise operation
b &= 1u << 3;
std::memcpy(&a, &b, 4);
float a = 1.4123;
unsigned int* inta = reinterpret_cast<unsigned int*>(&a);
*inta = *inta & (1 << 3);

Посмотрите на следующее. Вдохновлен быстрым обратным квадратным корнем:

#include <iostream>
using namespace std;

int main()
{
    float x, td = 2.0;
    int ti = *(int*) &td;
    cout << "Cast int: " << ti << endl;
    ti = ti>>4;
    x = *(float*) &ti;
    cout << "Recast float: " << x << endl;
    return 0; 
}

@самосуд:

Лучше:

#include <stdint.h>
...
union fp_bit_twiddler {
    float f;
    uint32_t u;
} q;

/* mutatis mutandis ... */

Для этих значений int, скорее всего, будет в порядке, но, как правило, для сдвига битов следует использовать целые числа без знака, чтобы избежать эффектов арифметических сдвигов. И uint32_t будет работать даже в системах, чьи числа не 32-битные.

FWIW, есть реальный вариант использования для побитовых операций с плавающей запятой (я только недавно столкнулся с этим) - шейдеры, написанные для графических процессоров, которые поддерживают только более старые версии GLSL (1.2 и более ранние версии не имели поддержки побитовых операторов) и где будет потеря точности, если поплавки будут преобразованы в целые.

Побитовые операции могут быть реализованы на числах с плавающей запятой с использованием остатков (по модулю) и проверок неравенства. Например:

float A = 0.625; //value to check; ie, 160/256
float mask = 0.25; //bit to check; ie, 1/4
bool result = (mod(A, 2.0 * mask) >= mask); //non-zero if bit 0.25 is on in A

Выше предполагается, что A находится между [0..1) и что в маске есть только один "бит" для проверки, но его можно обобщить для более сложных случаев.

Эта идея основана на некоторой информации, найденной в " это возможно для реализации побитовых операторов с использованием целочисленной арифметики"

Если нет даже встроенной функции мода, то это также может быть реализовано довольно легко. Например:

float mod(float num, float den)
{
    return num - den * floor(num / den);
}

Реализация Python в побитовых операциях с плавающей запятой (рецепт Python) побитовых операций с плавающей запятой работает путем представления чисел в двоичных числах, которые простираются бесконечно влево и вправо от дробной точки. Поскольку числа с плавающей запятой имеют знак нуля на большинстве архитектур, он использует их дополнение для представления отрицательных чисел (на самом деле он просто притворяется, что делает это, и использует несколько приемов для достижения внешнего вида).

Я уверен, что он может быть адаптирован для работы в C++, но необходимо соблюдать осторожность, чтобы не допустить переполнения правильных сдвигов при выравнивании показателей степени.

Битовые операторы НЕ должны использоваться на числах с плавающей точкой, так как числа с плавающей точкой зависят от аппаратного обеспечения, независимо от того, какое у вас может быть оборудование. Каким проектом / работой вы хотите рискнуть, "хорошо это сработало на моей машине"? Вместо этого для C++ вы можете получить аналогичное "чувство" для операторов сдвига битов, перегрузив оператор потока в обертке "объект" для float:

// Simple object wrapper for float type as templates want classes.
class Float
{
float m_f;
public:
    Float( const float & f )
    : m_f( f )
    {
    }

    operator float() const
    {
        return m_f;
    }
};

float operator>>( const Float & left, int right )
{
    float temp = left;
    for( right; right > 0; --right )
    {
        temp /= 2.0f;
    }
    return temp;
}

float operator<<( const Float & left, int right )
{
    float temp = left;
    for( right; right > 0; --right )
    {
        temp *= 2.0f;
    }
    return temp;
}

int main( int argc, char ** argv )
{
    int a1 = 40 >> 2; 
    int a2 = 40 << 2;
    int a3 = 13 >> 2;
    int a4 = 256 >> 2;
    int a5 = 255 >> 2;

    float f1 = Float( 40.0f ) >> 2; 
    float f2 = Float( 40.0f ) << 2;
    float f3 = Float( 13.0f ) >> 2;
    float f4 = Float( 256.0f ) >> 2;
    float f5 = Float( 255.0f ) >> 2;
}

У вас будет остаток, который вы можете выбросить в зависимости от желаемой реализации.

float a = 1.4123;
int *b = (int *)&a;
*b = *b & (1 << 3);
// a is now the IEEE floating-point value caused by the manipulation of *b
// equals 1.121039e-44 (tested on my system)

Это похоже на ответ Джастина, за исключением того, что он создает только представление битов в тех же регистрах, что и a, Поэтому, когда вы манипулируете *b, aзначение меняется соответственно.

Вы можете использовать указатели для переинтерпретации байтов без изменения фактических данных. Затем примените сдвиг (или/и другие побитовые операторы) и переинтерпретируйте байты обратно.

      #include <stdint.h>

float shiftLeftFloat(float val, int shiftCount) {
    uint32_t valAsInt = *((uint32_t *) &val);
    valAsInt <<= shiftCount;
    val = *((float *) &valAsInt);
    return val;
}          

Для x86-64 с gcc 11.2 (и флагом -O3), это компилируется в:

      movd    eax, xmm0 // copy `val` to eax register
mov     ecx, edi  // copy `shiftCount` to ecx register
sal     eax, cl   // shift eax by value of cl (= the lowest 8 bits of ecx)
movd    xmm0, eax // Move result (=eax) to "return" register (=xmm0)
ret

Примечание. Только младшие 8 бит shiftCountиспользуются. Поэтому входное значение всегда будет обрабатываться как shiftCount % 32.

salозначает shift arithmetic left

Другие вопросы по тегам