Подтвердить что ты не робот

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

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

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

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

Когда я это сделаю:

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

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

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

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

Я не понимаю, почему int можно отнести к void*, но не float.

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

4b9b3361

Ответ 1

На уровне языка нет такой вещи, как "побитовая операция с числами с плавающей запятой". Побитовые операции в 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, как предлагают другие ответы. Это не имеет большого смысла, это незаконно, и это не гарантирует работу компиляторов, которые следуют строгим правилам сглаживания при оптимизации. Единственный законный способ проверки содержимого памяти на С++ - это переинтерпретировать его как массив [signed/unsigned] char.

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

Ответ 2

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

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

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

Ответ 3

float a = 1.4123;
unsigned int* inta = reinterpret_cast<unsigned int*>(&a);
*inta = *inta & (1 << 3);

Ответ 4

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

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

Ответ 5

@mobrule:

лучше:

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

/* mutatis mutandis ... */

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

Ответ 6

FWIW, есть реальный вариант использования для побитовых операций с плавающей запятой (я только недавно столкнулся с этим) - шейдеры, написанные для реализаций OpenGL, которые поддерживают только более старые версии 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);
}

Ответ 7

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

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

Ответ 8

Побитовые операторы НЕ должны использоваться для поплавков, так как поплавки являются специфичными для аппаратного обеспечения, независимо от сходства того, что может быть у вашего оборудования. Какой проект/работа вы хотите подвергать риску "хорошо ли это работает на моей машине"? Вместо этого для С++ вы можете получить аналогичное "чувство" для операторов сдвига бит, перегружая оператор потока на "объектной" оболочке для 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;
}

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

Ответ 9

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)

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

Ответ 10

Вы можете обойти правило строгого псевдонима и выполнять побитовые операции над типом 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);