【问题标题】:Convert x >= y into 1 or 0 without branching or Boolean expressions将 x >= y 转换为 1 或 0 而不使用分支或布尔表达式
【发布时间】:2018-01-06 20:27:58
【问题描述】:

我需要在没有分支或布尔表达式的情况下实现以下功能:

uint8_t func(uint32_t num, uint8_t shl)
{
    if (num >= (1 << shl))
    {
        return shl;
    }
    else
    {
        return 0;
    }
}

我做的第一步是注意到else 部分很简单:

return num / (1 << shl);

当然,在if 部分执行此操作不一定会产生预期的结果。

所以我想我需要一些比num / (1 &lt;&lt; shl) 更“聪明”的东西。

如果我能想出一个可以给我10 的表达式,那么我就完成了。

是否有某种方法可以仅使用算术/按位运算(即,没有分支或布尔表达式)?

谢谢。

【问题讨论】:

  • @lurker:没有家庭作业(这又有什么关系呢??????)。我需要避免分支以优化性能。我实际上是从循环内部调用此代码,而不是在函数中(此处描述是为了简化问题并专注于问题)。
  • @goodvibration 你用的是什么编译器?生成的 asm 是什么样子的?
  • 根据备选方案,分支可能不是最理想的。为什么不问问实现该功能的最佳方式是什么?答案可能还取决于平台和编译器。
  • @goodvibration 这不是这个网站的运作方式。问题应尽可能清晰准确,以便未来的读者易于回答和搜索。
  • 过早的优化是邪恶的。在尝试手动优化之前,您必须证明代码中存在性能问题。然后,请修复您的? 密钥。

标签: c bit-manipulation integer-arithmetic


【解决方案1】:

您可以将条件视为计算结果为 TRUE (1) 或 FALSE (0) 的布尔表达式,因此您可以使用以下内容:

return (num >= (1 << shl)) * shl;

这段代码通常不是一个好主意,但在无分支约束下,它可以完成工作。

【讨论】:

  • @goodvibration 分支是一个条件跳转。这些指令不会产生任何条件分支。而是完成了 比较 操作。以防万一,您应该指定您不需要比较运算,而只需要算术和移位
  • @goodvibration 是的,但你说“没有分支”。这个答案确实没有分支。
  • "我无法在我的平台上将 bool 转换为 uint"
  • @PeterJ 你在说什么?没有人使用?:
  • 除非机器有条件地执行指令来赋值,我怀疑你是否可以在没有条件分支指令的情况下评估&gt;=。对于编译器,这与if ... else ... 没有太大区别。虽然您可以避免在 C 中使用 if,但编译器无法避免创建指令来获取任一结果值。
【解决方案2】:

怎么样?

bool func( int32_t num, int8_t shl)
{
  return (num>=(1<<shl));
}
  • Return 1 if x&gt;=(1&lt;&lt;y)
  • Return 0 if x&lt;(1&lt;&lt;y)

【讨论】:

  • 检查早在此之前发布的另一个(相同)答案。
  • 与显示的代码不同。现有的答案很好。
  • 对不起,我没有刷新网站
【解决方案3】:

如果您想避免比较运算符,可以使用减法和位移来探测符号位:

uint8_t func(uint32_t num, uint8_t shl) {
    return shl * (1 - ((num-(1<<shl)) >> (sizeof(num) * CHAR_BIT -1)));
} 

Demo.

【讨论】:

  • 31 代表sizeof(num) * CHAR_BIT -1?
  • 顺便说一句,这通常是错误的。无符号类型可能包含填充位。 Here's a safe way to know the number of value bits。然后,我不会谈论无符号类型中的 符号位
  • 是的,num-(1&lt;&lt;shl) 部分让我很担心。
  • 但是,由于uint32_t 是一个精确大小的类型,我们知道它有 32 个值位。所以在这里硬编码31 是安全的,而sizeof(num) * CHAR_BIT -1 有可能返回更大的数字。
  • 使用(uint32_t) 1 &lt;&lt; shl 比使用(int)1&lt;&lt;shl 更有意义。
【解决方案4】:

假设您的实现执行arithmetic shift(通常是这种情况)以及二进制补码算法,您可以利用符号位传播:

uint8_t func(uint32_t num, uint8_t shl)
{
    return (-(int32_t)(num >> shl) >> 31) & shl;
}

这可能会转化为无分支的 x86 汇编代码:

func(unsigned int, unsigned char):
  mov eax, edi
  mov ecx, esi
  shr eax, cl
  neg eax
  sar eax, 31
  and eax, esi
  ret

主要思想是&amp; 的左操作数要么全为1,要么全为零,这意味着shl 的值要么被保留,要么被清零。

最棘手的部分是-(int32_t)(num &gt;&gt; shl)。它建立在以下事实之上:

  • num / (1 &lt;&lt; shl) 表达式与num &gt;&gt; shl 基本相同
  • 无符号值的右移总是用零填充左边的位
  • shl == 0时,结果总是0(即&amp;的左操作数的值是无意义的,因为它无论如何都会被shl丢弃)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-28
    • 2013-10-07
    • 2018-12-02
    • 1970-01-01
    • 2017-12-27
    • 2011-12-03
    相关资源
    最近更新 更多