【问题标题】:C++ XOR swap on float values浮点值上的 C++ XOR 交换
【发布时间】:2018-09-20 16:02:53
【问题描述】:

是否可以在 c++ 中使用带有浮点值的 XOR 交换算法?

但是维基百科说:

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

它似乎有效(至少在 gcc 下),但我担心如果需要,是否允许这种做法?

【问题讨论】:

  • 请记住,浮点变量的数据仅存储为 0 和 1,就像二进制计算机上的任何其他数据和值一样。无论这些位代表什么,对位的操作都是一样的。
  • 有什么问题?您从 Wikipedia 引述说它可能的,这是正确的,因为@Someprogrammerdude 所说的:D
  • 请不要使用xor 技巧。除非您的内存太少以至于无法负担额外的整数(甚至现代嵌入式系统都有大量内存),否则它是没有用的,而且它可能比简单的临时变量交换。它应该归于我们这些老人的时代,他们仍然深情地记得将他们的代码塞进 1K ZX80 :-)
  • 通过指向不同的、不相关类型的指针访问数据是未定义的行为。还是我错过了 C++ 标准的一些新发展?
  • 除了这是否能正常工作的问题之外,试着用它自己交换一个值。

标签: c++ floating-point swap xor


【解决方案1】:

从技术上讲,您所问的可能的,但正如 IInspectable 明确评论的那样,它会导致 UB(未定义行为)。
无论如何,我建议你改用std::swap,它是一个通常专门针对特定数据类型的模板,旨在做好工作。

【讨论】:

  • §3.10 p 10 [basic.lval]: "如果程序试图通过非下列类型之一的左值访问对象的存储值,则行为未定义: [...]" - 所以,是的,可能的,似是而非的,干净的。并且错了。这是未定义的。
  • 抱歉,它不会导致未定义的行为。它根本做不到,该算法独立于交换信息的二进制解释。字节模式的交换与通过分配相同。恐怕我们在 UB 问题上走得太远了。这就像说通过通讯线路传输位模式会产生未定义的行为,因为浮点中的位作为位而不是双精度发送。
【解决方案2】:

如果int 的大小与float 相同,那么它将在任何合理的架构上实际工作。浮点数中的内存是一组位,您可以解释这些位并使用 xor 操作完全交换它们。然后,您可以再次使用这些位作为正确的floats。您引用的引用仅表示您交换的两个值需要是相同类型的,并且都是ints。

但是,在某些架构上,这可能会导致不同类型的寄存器之间的移动,或显式地将寄存器刷新到内存中。如今,您将在几乎所有具有健全优化编译器的健全架构上看到的是,使用std::swap 或带有临时变量的表达式的显式交换实际上更快。

即你应该写:

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);

如果您架构的标准库作者确定异或交换确实是有益的,那么您应该希望最后一种形式会使用它。就在不必要的神秘实现中隐藏意图而言,异或交换是一个典型的坏习惯。只有在优化器严重不足且优化器严重不足的情况下,它才有效。

【讨论】:

  • 如果 int 和 float 具有相同的大小,xor-trick 也会失败,但 int 有更严格的对齐要求。我承认这不太可能。
  • @HansOlsson:相反,使用专用的 FPU 电路,浮点类型和整数类型之间的对齐要求不同的可能性更大。由于 FPU 计算通常以 SIMD 单元执行,因此需要对齐通常与通用 CPU 寄存器不同。
  • @IInspectable 特殊的 SIMD 单元可能对浮点数有更严格的对齐方式,但 xor-trick 的问题只有在 int 的对齐要求更严格时才会出现。
【解决方案3】:

您的代码会调用未定义的行为。在 C 或 C++ 中,将 float* 转换为 int* 并照此使用是不合法的。 reinterpret_cast 应该用于在具有兼容布局的不相关结构之间进行转换,或者在类型指针和void* 之间进行临时转换。

哦,在这种特殊情况下,UB 不仅仅是学术问题。编译器可能会注意到xorSwap() 没有触及任何floats,执行语言的别名规则允许的优化,并打印出ab 的原始值而不是交换的值.这甚至不涉及 intfloat 具有不同大小或对齐方式的架构。

如果您想安全地执行此操作,则必须将 memcpy() 从浮点数转换为无符号字符数组,在循环中执行 XOR,然后返回 memcpy()。这当然会使操作比正常交换慢。当然,基于异或的交换已经比普通交换慢了。

【讨论】:

  • " reinterpret_cast 应该用于在 ... 类型化指针和 void* 之间进行转换"。你不会为此使用static_cast 吗?
  • @user2079303 在那种情况下,两者具有相同的功能,但我更喜欢reinterpret_cast,只是因为它看起来吓人到适合这种情况的程度。当然,一开始在 C++ 中使用void* 是非常罕见的,所以这个问题并不经常出现。
【解决方案4】:

它是:

a) 在编译器允许的情况下可能。

b) 标准未定义行为的操作(即未定义的行为)

c) 在 gcc 上,实际上效率低于准确说明您想要的内容:

给定:

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

【讨论】:

    猜你喜欢
    • 2014-04-17
    • 2014-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-20
    相关资源
    最近更新 更多