2017-02-06 5 views
0

Возможно ли использовать алгоритм замены XOR с поплавковыми значениями в C++?C++ XOR swap on float values ​​

Однако википедия говорит, что:

XOR операции побитовое поменять значения различных переменных, имеющих один и тот же тип данных

, но я немного запутался. С помощью этого кода:

void xorSwap (int* x, int* y) { 
    *x ^= *y; 
    *y ^= *x; 
    *x ^= *y; 
} 

int main() { 

    float a = 255.33333f; 
    float b = 0.123023f; 
    xorSwap(reinterpret_cast<int*>(&a), reinterpret_cast<int*>(&b)); 
    std::cout << a << ", " << b << "\n"; 

    return 0; 
} 

это, кажется, работает (по крайней мере в ССЗ), но я обеспокоен, если такая практика допускается, если это необходимо?

+1

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

+0

В чем проблема? Ваша цитата из Википедии говорит, что это возможно, и это правильно из-за того, что @Someprogrammerdude сказал: D – ForceBru

+3

Пожалуйста, не используйте трюк 'xor'. Это бесполезно, если у вас не хватает памяти, что вы не можете позволить себе дополнительный целочисленный (и даже современные встроенные системы имеют bucketloads памяти), и это, вероятно, * медленнее *, чем простая замена переменной temp. Это должно быть отнесено ко временам старых людей, которые по-прежнему с любовью вспоминают, как tring втискивает свой код в 1K ZX80 :-) – paxdiablo

ответ

6

Технически, что вы просите , возможно, но, как ясно прокомментировал IInspectable, он вызывает неопределенное поведение.
В любом случае, я предлагаю вам вместо этого использовать std::swap, это шаблон, который часто специализируется на определенных типах данных и предназначен для хорошей работы.

+2

§3.10 p 10 [basic.lval]: * «Если программа пытается получить доступ к сохраненному значению объекта через значение gl другого, чем одно из следующих типов, поведение не определено: [...]« * - So , да, возможно, правдоподобно, чисто. И неправильно. Это не определено. – IInspectable

+0

Извините, это не вызывает неопределенное поведение. Это просто не может, алгоритм не зависит от бинарной интерпретации информации, замененной. Шаблоны байтов меняются местами так же, как и с помощью назначений. Боюсь, мы заходим слишком далеко с проблемой UB. это похоже на то, что передача битового шаблона через линию связи создает неопределенное поведение, поскольку биты в float отправляются как биты, а не как double. –

1

Если int такого же размера, как float, он будет работать на практике по любой разумной архитектуре. Память в поплавке представляет собой набор бит, вы интерпретируете эти биты и полностью их заменяете с помощью операций xor. Затем вы можете использовать эти биты как правильные float. Цитата, на которую вы ссылаетесь, говорит только о том, что два значения, которые вы меняете, должны быть одного типа, а оба - int.

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

I.e. Вы должны написать:

float a = 255.33333f; 
float b = 0.123023f; 
float tmp = a; 
a = b; 
b = tmp; 

или предпочтительно:

float a = 255.33333f; 
float b = 0.123023f; 
std::swap(a,b); 

Если стандартная библиотека автор для вашей архитектуры определил XOR подкачки быть действительно полезными, то вы должны надеяться, что последняя форма будет использовать его. xor swapping - типичная плохая идиома с точки зрения скрытия намерений в тайной тайной реализации. Это было только когда-либо эффективно в серьезных регистрах с голоду с плохими оптимизаторами.

+0

xor-трюк также потерпит неудачу, если int и float имеют одинаковый размер, но int имеет более строгие требования к выравниванию.Я признаю, что это маловероятно. –

+0

@ HansOlsson: Напротив, при использовании специального кода FPU различные требования к выравниванию между типами с плавающей точкой и целыми типами более вероятны. Тем более что вычисления FPU часто выполняются в модулях SIMD, которые требуют выравнивания, обычно отличного от того, что для регистров CPU общего назначения. – IInspectable

+0

@Инспективные Специальные блоки SIMD, вероятно, будут иметь более строчное выравнивание для поплавков, но проблема с xor-трюком возникает только в том случае, если требование выравнивания для int более строгое. –

1

Ваш код вызывает неопределенное поведение. В C или C++ не разрешено приводить float* в int* и использовать его как таковой. reinterpret_cast следует использовать для преобразования между несвязанными структурами с совместимыми макетами или для временного преобразования между типизированным указателем и void*.

О, и в данном конкретном случае УБ не является просто академической проблемой. Компилятор может заметить, что xorSwap() не касается каких-либо float, выполнив оптимизацию, разрешенную им правилами псевдонимов языка, и распечатайте исходные значения a и b вместо измененных значений. И это даже не входит в архитектуры, где int и float имеют разные размеры или выравнивания.

Если вы хотите сделать это безопасно, у вас должно быть memcpy() из ваших поплавков в неподписанные массивы символов, выполните XOR в цикле, затем memcpy() назад. Это, конечно же, сделает операцию медленнее обычной замены. Конечно, обмен на основе xor УЖЕ медленнее, чем обычная замена.

+0

"' reinterpret_cast' следует использовать для преобразования между ... типизированным указателем и void * ". Разве вы не использовали бы для этого 'static_cast'? – user2079303

+0

@ user2079303 Эти две функции имеют эквивалентную функциональность в этой ситуации, но я предпочитаю «reinterpret_cast», просто потому, что выглядит страшно до уровня, соответствующего ситуации. Конечно, довольно редко использовать 'void *' в C++, поэтому вопрос не часто возникает. – Sneftel

1

Это:

а) возможно, когда компилятор позволяет.

б) операция, для которой стандарт не определяет поведение (т.е. не определено поведение)

с) на ССЗ, на самом деле менее эффективны, чем о том, что именно вы хотите:

Дано:

void xorSwap (unsigned int* x, unsigned int* y) { 
    *x ^= *y; 
    *y ^= *x; 
    *x ^= *y; 
} 

void swapit3(float& a, float&b) 
{ 
    xorSwap(reinterpret_cast<unsigned int*>(&a), reinterpret_cast<unsigned int*>(&b)); 
} 

результаты в этом:

swapit3(float&, float&):       # @swapit3(float&, float&) 
     mov  eax, dword ptr [rdi] 
     xor  eax, dword ptr [rsi] 
     mov  dword ptr [rdi], eax 
     xor  eax, dword ptr [rsi] 
     mov  dword ptr [rsi], eax 
     xor  dword ptr [rdi], eax 
     ret 

тогда как это:

void swapit2(float& a, float&b) 
{ 
    std::swap(a,b); 
} 

результаты в этом:

swapit2(float&, float&):       # @swapit2(float&, float&) 
     mov  eax, dword ptr [rdi] 
     mov  ecx, dword ptr [rsi] 
     mov  dword ptr [rdi], ecx 
     mov  dword ptr [rsi], eax 
     ret 

ссылка: https://godbolt.org/g/K4cazx