【问题标题】:Is there a one-liner for subtracting 1 from an input if the input is greater than zero?如果输入大于零,是否有一个从输入中减去 1 的单行程序?
【发布时间】:2016-03-19 05:11:20
【问题描述】:

也许我忘记了一个巧妙的按位运算,但有没有更聪明的方式来表达以下内容?

int r = foo(); // returns an int
r -= r > 0; // same as `if(r > 0) r--;`

我想把这些行合并成一个表达式。

【问题讨论】:

  • 定义“更聪明”。 r = max(r - 1, 0); 是这样的“夹紧”操作的常用措辞。
  • @JonPurdy 我的意思是“更聪明”,因为消除条件。限制价值不是我的目标。 r 可以是负值,在这种情况下不起作用。
  • 啊,对。那为什么不简单地写下你的意思呢? if (r > 0) --r;
  • language-agnostic 怎么样?您的示例看起来像 C,这样的问题在声明性低级语言领域之外没有多大意义。 (根据问题标题,我希望能够贡献awk '$1 > 0 { --$1 } 1',这在 Awk 脚本的世界中绝对是“单行”,但这显然不是您真正要问的。)
  • 无论如何,试图找到一种“聪明”(即或多或少深奥)的方式来混淆应该清楚表达的内容听起来并不值得。

标签: c++ c integer boolean-operations


【解决方案1】:

最易读的方式是最聪明的方式:

if (r > 0)
    --r;

它是可读的,并且最有可能生成最快的代码 - 因为许多开发人员不会徒劳地试图看起来很聪明,而且创建优化编译器的人会检测常见模式并对其进行优化。

【讨论】:

  • 不幸的是,您的“最快代码”论点是否在实践中发挥作用尚不清楚。我在 x86-64 上使用 gcc -O3 尝试了您的代码和 OP 的 r -= (r > 0)。你的生成一个条件分支,而使用 OP 的代码,我们得到一个条件集并且没有分支。在没有上下文的情况下,“实际上”更快的测试很难,但在所有其他条件相同的情况下,人们预计分支会更慢。
  • 如果这是在图形渲染代码的内部循环中,那么减少分支很重要。否则写清楚正确的代码
  • @pm100:真的那么重要吗?据我所知,分支预测是现代处理器的一个非常基本的特性。
  • 阅读这篇文章 - 向下滚动到“可以做什么”,您会发现用条件或按位操作替换分支会产生很大的不同stackoverflow.com/questions/11227809/…
【解决方案2】:

出于可读性考虑,我个人更喜欢if (r > 0) r--;,但如果你真的想避免使用条件:

r = r > 0 ? r-1 : r;

这仍然是一个条件赋值,或者:

r > 0 && r--;

或:

r <= 0 || r--;

这是可行的,因为如果左侧已经确定结果,右侧将不会被执行。

顺便说一句,不要指望任何性能差异。大多数编译器会从所有这些选项中生成或多或少同等性能的代码。

【讨论】:

  • r &gt; 0 时,r = r--; 不会是未定义的吗? r = r &gt; 0 ? r-1 : r; 会更安全。
  • “大多数编译器会从所有这些选项中生成或多或少相同的代码。”你试过了吗?对我来说,x86-64 上的gcc -O3 为您的第一个示例(条件集和减法)生成与其他示例(条件跳转)截然不同的代码。 clang 也一样。
  • @NateEldredge:不,我没有尝试过。我的真正意思是,这绝对不是程序员应该尝试优化的那种代码。稍微改变了我的措辞。
  • r = r &gt; 0 ? r-1 : r;r -= r &gt; 0 ? 1 : 0; 不同吗?
  • @yuki96:效果是一样的,但对于某些——可能是不合理的——原因我不喜欢将-=+=? :一起使用。
【解决方案3】:

如果测试是 r >= 0,那么一种可能性是隔离符号位,然后减去 (1-signBit)。

在 C 中,类似这样的东西(但我想这可以转换成多种语言)

int r = foo();
return r - (int)((~ ((unsigned) r)) >> (sizeof(r)*8-1));

当然,您现在依赖于 int 的内部表示并且大大混淆了代码...

对于 r > 0,它的参与度更高......也许是这样的

unsigned int r = foo();
return (int) (r - ((~(r | (r-1))) >> (sizeof(r)*8-1)));

我什至没有尝试过那些 sn-ps,答案是为了好玩,但如果你认真对待它,你将不得不提供大量的解释来证明这种 hack 的合理性,包括测试、生成的代码分析和基准。

如果像这样的每一个小操作都经过这样一个过程,那么编写代码会很痛苦,对于需要维护它的人来说更痛苦(阅读/理解/扩展......)。我希望这不是过早的优化,在这种情况下,也许正确的决定是编写一小段汇编程序。

【讨论】:

  • 哎呀,看起来很乱。我希望像r = ~-r; 这样快速而肮脏的东西会存在,但我想不会。
  • 啊,看看我的回答,我可能错过了unsigned 演员表。 @ yuki96 避免条件跳转可能是正确的,但是为了代码的清晰性,您可能可以使用条件 MOVE。 cmov(或其模拟)非常便宜,可能是最好的选择。如果 Nate 是对的,r = r &gt; 0 ? r-1 : r; 可能是最好的选择
【解决方案4】:

嗯,比如说,对于带有 2 补码的 32 位整数,这样的方法可能有效(未经测试!)

const int mask = 1 << 31;

int r = foo();

r -= ((r ^ mask) >> 31);

基本上,您检查最左边的位是否为零,如果为零,则将其转换为 1,否则仍将为零,然后将其移位,使 1 表示正数,0 表示负数,然后减去

【讨论】:

  • 首先,除非非常必要,否则不要使用实现定义的行为,其次,不要发布未经测试的代码作为答案。
  • @user3528438 哈哈哈11th RULE: NEVER EVER make up rules for Stack Overflow!
猜你喜欢
  • 1970-01-01
  • 2021-12-31
  • 2020-08-11
  • 2016-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-13
相关资源
最近更新 更多