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