寻找最小正数的最简洁/最快算法

7

简单问题 - 在c++中,最简洁的方法是获取哪个数字(u0和u1)是最小的正数?(仍然有效)

我尝试的每种方法都涉及大量if语句或复杂的条件语句。

谢谢, 丹

这里是一个简单的例子:

bool lowestPositive(int a, int b, int& result)
{
    //checking code
    result = b;
    return true;
}


lowestPositive(5, 6, result);

抱歉,如果两个都是负数,答案是什么? - Brent.Longborough
返回假!即不返回任何答案,我会编辑问题以提供示例... - Dan
结果应该是什么?0?不变?还是两者中的最小绝对值? - Joel Coehoorn
16个回答

16

如果这些值是用二进制补码表示的,则

result = ((unsigned )a < (unsigned )b) ? a : b;

当作为无符号数处理时,二进制补码中的负数值比正数值大,因此这种方法可行。与Jeff的答案类似,这需要至少有一个值是正数。

return result >= 0;

聪明。有点笨拙,但仍然很聪明。 - Tall Jeff
如果两个参数都是负数,那么结果也是负数,并且函数返回false,因此在这种情况下result的具体值并不重要。 - jfs
1
不清楚 OP 是否考虑正数或者非负数,因此我针对正数即排除零的情况发布了一个答案。https://dev59.com/D0bRa4cB1Zd3GeqPxBZJ - jfs
最好将方法封装在函数内部,这样可以更方便地重用它。(更好的做法是,您可以使用模板进行泛化)此外,通过类型转换您会丢失效率,作者应确保a和b最初是无符号的。 - Tamara Wijsman
这不应该失去任何效率。在汇编级别上,它只会使用无符号比较操作。在二进制补码中,从int到unsigned int的转换时,位模式不会改变。 - Johannes Schaub - litb
显示剩余3条评论

12

我更喜欢清晰明了而不是紧凑简洁:

bool lowestPositive( int a, int b, int& result )
{
   if (a > 0 && a <= b) // a is positive and smaller than or equal to b
      result = a;
   else if (b > 0) // b is positive and either smaller than a or a is negative
      result = b;
   else
      result = a; // at least b is negative, we might not have an answer

   return result > 0;  // zero is not positive
}

很好。我也喜欢这种清晰度 :) - Gant
没有输入符号的限制。 :) - tvanfosson
几乎完美,可能总是更快地在赋值之后立即返回 true 或 false(实际上,gcc 现在测试结果,即使在达到返回语句时已经知道结果为正) - mjy
正如@HugoHeden的答案指出的那样,如果a>0b<0,这种方法就会失败。他的答案基本上是使用模板完成的,并且在第一个if()中增加了额外的条件。 - Peter Cordes
@HugoHeden 很好,问题已修复。 - tvanfosson

3
unsigned int mask = 1 << 31;
unsigned int m = mask;
while ((a & m) == (b & m)) {
  m >>= 1;
}
result = (a & m) ? b : a;
return ! ((a & mask) && (b & mask));

编辑:我觉得这并不太有趣,所以我删除了它。但是再想一想,还是留在这里好玩:) 这可以被认为是Doug答案的倾倒版本 :)


这个代码被评为负分是荒谬的。它做了它应该做的事情,而且我认为相当聪明。所以我投+1。 - mstrobl
这个主题很有趣,让我们玩一下吧。公主,为什么我们总是那么认真呢? :) 嘿,这是聪明的代码。为什么要把它浪费在 -1 的尘世中呢? - mstrobl
为了应对不是32位宽度的整数,可以使用像(1 << std::numeric_limits<int>::digits)这样的掩码,或者不使用模板的方式 ((1 << (CHAR_BIT * (sizeof(int)-1))) << (CHAR_BIT-1))。 - Johannes Schaub - litb
没错,它并不适用于不同的大小。另一种方法是 (unsigned int) -1 ^ ((unsigned int) -1 >> 1),这应该在几乎任何 C 编译器上都可以工作。 - PolyThinker

3

可能会被评为“负分”,但只是为了好玩,这里展示一下没有任何比较的结果,因为比较是弱者的表现。:-)

bool lowestPositive(int u, int v, int& result)
{
  result = (u + v - abs(u - v))/2;
  return (bool) result - (u + v + abs(u - v)) / 2;
}

注意:如果(u + v)> max_int,则失败。至少有一个数字必须是正数才能正确返回代码。同时赞扬polythinker的解决方案 :)

1
是的,它被称为数学。而且它旨在以嬉皮笑脸的方式阅读。 :) - mstrobl
@dmckee:也许这样读起来更好:“midpoint=(u+v)/2;dist=abs(u-v)/2; result = midpoint-dist;”--这表明result始终获得两个值中较小的一个(无论符号如何),但“return (bool)result-(midpoint+dist);”除非u=v,否则返回true:所以它是错误的:P - ShreevatsaR
@ShreevatsaR:我必须承认,我没有测试代码。分析得很好!;) 不正确的返回值归结于我的思维错误。return result > 0是正确的,但不幸的是其中有一个软弱的比较。 - mstrobl
1
不要误会我。我很受感动。故意使用错误的工具很酷,但只有在真的很笨拙的情况下才是如此。这个符合条件。 - dmckee --- ex-moderator kitten
除了被接受的答案外,我发现这个答案最容易理解 :) - Lamar Latrell

3

这是一个使用位操作技巧来查找min(x, y)的快速解决方案。它是@Doug Currie的答案的修改版本,并受到该问题的答案的启发:查找最小正值

bool lowestPositive(int a, int b, int* pout)
{
  /* exclude zero, make a negative number to be larger any positive number */
  unsigned x = (a - 1), y = (b - 1);    
  /* min(x, y) + 1 */
  *pout = y + ((x - y) & -(x < y)) + 1; 
  return *pout > 0;
}

例子:

/** gcc -std=c99 *.c && a */
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdbool.h>

void T(int a, int b) 
{           
  int result = 0;   
  printf("%d %d ", a, b);       
  if (lowestPositive(a, b, &result))    
    printf(": %d\n", result);       
  else              
    printf(" are not positive\n");  
}

int main(int argc, char *argv[])
{
  T(5, 6);
  T(6, 5);
  T(6, -1);
  T(-1, -2);

  T(INT_MIN, INT_MAX);
  T(INT_MIN, INT_MIN);
  T(INT_MAX, INT_MIN);
  T(0, -1);
  T(0, INT_MIN);
  T(-1, 0);
  T(INT_MIN, 0);
  T(INT_MAX, 0);
  T(0, INT_MAX);
  T(0, 0);

  return 0;
}

输出:

5 6 : 5
6 5 : 5
6 -1 : 6
-1 -2  are not positive
-2147483648 2147483647 : 2147483647
-2147483648 -2147483648  are not positive
2147483647 -2147483648 : 2147483647
0 -1  are not positive
0 -2147483648  are not positive
-1 0  are not positive
-2147483648 0  are not positive
2147483647 0 : 2147483647
0 2147483647 : 2147483647
0 0  are not positive

2

我建议您将该函数重构为更简单的函数。此外,这可以让您的编译器更好地强制执行预期的输入数据。

unsigned int minUnsigned( unsigned int a, unsigned int b )
{
   return ( a < b ) ? a : b;
}

bool lowestPositive( int a, int b, int& result )
{
   if ( a < 0 && b < 0 )  // SO comments refer to the previous version that had || here
   {
       return false;
   }

   result = minUnsigned( (unsigned)a, (unsigned)b );  // negative signed integers become large unsigned values
   return true;
}

这适用于ISO C允许的所有三种有符号整数表示:二进制补码、反码和原码。我们只关心任何正的有符号整数(MSB清除)比任何设置了MSB的东西更低。
实际上,这在x86上使用clang编译器可以编译出非常好的代码,如在Godbolt编译器浏览器上可以看到。很遗憾,gcc 5.3的表现要差得多


返回最高正数。 - Doug Currie
a = -1 b = 1应该返回1,但实际上没有。 - pro
根据问题集,我正在处理大于0的整数集,因为我的if语句强制执行这一点,并且这也是OP所要求的。此外,我试图证明的是,通过将函数重构为更小的部分,逻辑变得更简单。 - antik
1
你第一个if语句的意思是错误的。如果a和b都是正数,它将返回false,而不是当a和b都是负数时返回false。 - tvanfosson
有趣的使用无符号整数将负数转换为比最大有符号整数更大的数字。不过,这需要加上注释! - Peter Cordes

2
这将处理您请求的所有可能输入。
bool lowestPositive(int a, int b, int& result)
{
    if ( a < 0 and b < 0 )
        return false

    result = std::min<unsigned int>( a, b );
    return true;
}

话虽如此,您提供的签名允许出现隐蔽的错误,因为很容易忽略此函数的返回值,甚至不记得必须检查返回值以确定结果是否正确。

您可能更喜欢以下一种方案,它使得成功结果不易被忽视:

boost::optional<int> lowestPositive(int a, int b)
{
    boost::optional<int> result;
    if ( a >= 0 or b >= 0 )
        result = std::min<unsigned int>( a, b );
    return result;
}

或者

void lowestPositive(int a, int b, int& result, bool &success)
{
    success = ( a >= 0 or b >= 0 )

    if ( success )
        result = std::min<unsigned int>( a, b );
}

2

这里很多答案忽略了零不是正数的事实 :)

通过巧妙的类型转换和三目运算符:

bool leastPositive(int a, int b, int& result) {
    result = ((unsigned) a < (unsigned) b) ? a : b;
    return result > 0;
}

不那么可爱:

bool leastPositive(int a, int b, int& result) {
    if(a > 0 && b > 0)
        result = a < b ? a : b;
    else
        result = a > b ? a : b:
    return result > 0;
}

1
没有花哨,合理的清晰度,适用于整数和浮点数:
template<class T>
  inline
  bool LowestPositive( const T a, const T b, T* result ) {
  const bool b_is_pos = b > 0;
  if( a > 0 && ( !b_is_pos || a < b ) ) { 
    *result = a;
    return true;
  }
  if( b_is_pos ) {
    *result = b; 
    return true;
  }
  return false;
}
  • 请注意,0(零)不是正数。
  • OP要求处理数字(我将其解释为整数和浮点数)。
  • 仅在有积极结果时解引用结果指针(性能)
  • 仅对a和b进行一次正测试(性能--不确定这样的测试是否昂贵?)

还要注意接受的答案(由tvanfosson提供)是错误的。如果a为正且b为负,则失败(说“两者都不是正数”)。 (这是我添加单独答案的唯一原因--我没有足够的声誉添加评论。)


1

使用“魔数”-1 进行黑客攻击:

enum
{
    INVALID_POSITIVE = -1
};

int lowestPositive(int a, int b)
{
    return (a>=0 ? ( b>=0 ? (b > a ? a : b ) : INVALID_POSITIVE ) : INVALID_POSITIVE );
}

这不会假设数字为正数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接