Как выполнить побитовую операцию над числами с плавающей запятой
Я попробовал это:
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