【问题标题】:Fastest way to flip the sign of a double / float in C在C中翻转双/浮点符号的最快方法
【发布时间】:2011-07-09 23:00:32
【问题描述】:

在 C 中翻转双精度(或浮点)符号的最快方法是什么?

我认为直接访问符号位是最快的方法,发现如下:

double a = 5.0;
*(__int64*)&a |= 0x8000000000000000;
// a = -5.0

float b = 3.0;
*(int*)&b |= 0x80000000;
// b = -3.0

但是,以上不适用于负数:

double a = -5.0;
*(__int64*)&a |= 0x8000000000000000;
// a = -5.0

【问题讨论】:

  • 该代码完全不可移植。您不仅调用了各种平台相关的 float 实现,还使您的代码依赖于字节序。
  • 它也可能会破坏寄存器存储浮点数的性能 - 它需要移动到整数寄存器,执行操作,然后移回 FP (x87/SSE)注册。
  • 我很想知道什么计算有浮点否定作为它的性能瓶颈
  • @Yann Ramin:GCC 将“-a”优化为“XORPS %XMM1, %XMM0”(GNU 语法),XMM1 在 x86_64 上持有否定位掩码,在 x86_32 上使用 FCHS。
  • 你的代码也严重违反了别名规则,这意味着它在现代编译器上不会做你想做的事情

标签: c floating-point double bit-manipulation sign


【解决方案1】:

如果你想要可移植的方式,只需乘以-1 并让编译器对其进行优化。

【讨论】:

  • a*=-1 和 a=-a 有区别吗?我怀疑第一个变体实际执行乘法而不是仅仅翻转符号位因此变慢。
  • @hennes a) 是否存在差异取决于编译器的实现。 b) 为什么你会怀疑优化器不起作用?
  • @Jim 是的,看来你是对的。对不起,我只是好奇。我想我应该加强对现代编译器的信任。
  • 差别很大。乘法被认为是一种算术运算,并且必须遵循一组特定的规则,例如设置浮点异常标志。否定不是,因此可以通过切换符号位来实现。 (但是对于整数,它们可以被认为是相同的。)
  • @Lindydancer:我也是这么想的。但是,我在 GCC 和 Intel 的编译器上检查了它,并没有发现 a=-a、a*=-1 和 a*=-1.0 之间的性能差异。所以我猜它们都被编译器以同样的方式优化了。
【解决方案2】:

如果您只是在前面加上一个否定运算符,即-a,任何体面的编译器都会实现这种位操作。无论如何,你是在 OR-ing 位。你应该 XOR 它。这就是我测试过的编译器所做的(GCC、MSVC、CLang)。所以请帮自己一个忙,写信-a

编辑:请注意,C 不强制执行任何特定的浮点格式,因此对非整数 C 变量的任何位操作最终都会导致错误行为。


EDIT 2 due to a comment:这是 GCC 为 x86_64 发出的否定代码

.globl neg
    .type   neg, @function
neg:
.LFB4:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    movq    %rsp, %rbp
    .cfi_offset 6, -16
    .cfi_def_cfa_register 6
    movss   %xmm0, -4(%rbp)
    movss   -4(%rbp), %xmm1
    movss   .LC0(%rip), %xmm0
    xorps   %xmm1, %xmm0  /* <----- Sign flip using XOR */
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE4:
    .size   neg, .-neg

需要注意的是xorps是为浮点数设计的异或,照顾特殊情况。这是一条 SSE 指令。

【讨论】:

  • Any decent compiler will implement this bit manipulation 这是错误的。编译器需要考虑NaN 值。 -NaN 仍然是 NaN,但 NaN xor 符号位不再是 NaN。编译器不能也不会进行这种优化。
  • 你看过实际的二进制输出了吗?我为提到的编译器做了它,他们做到了。
  • 感谢发布反汇编。 VC 不对符号位进行异或运算,它使用fchs。我对 gcc 这样做感到有些震惊……这是与 -O2 一起使用的吗?不传播的 NaN 可能会导致严重的错误。
  • -log(-1.0f) 的输出是什么? nan2.14748e+09?
  • @inverse:你在说什么?在 IEEE 中,NaN 被编码,指数部分设置为全 1,任何非零尾数(尾数零表示无穷大)。符号位无关紧要。安静或信令是(在实践中)尾数中的最高幅度位。同样,不是符号位。
【解决方案3】:

a=-a

【讨论】:

  • 这将是最有效的方法。浮点数的位操作不可移植,永远不要使用位运算符接触浮点数。把它留给编译器。
【解决方案4】:

此代码未定义,因为它违反了严格的别名规则。 What is the strict aliasing rule? 要做到这一点,您将不得不依赖编译器为您优化它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-11
    • 2016-03-08
    • 1970-01-01
    • 1970-01-01
    • 2014-04-19
    • 2021-12-04
    相关资源
    最近更新 更多