在不使用条件语句和三元运算符的情况下,在 C 中查找最多三个数

Posted

技术标签:

【中文标题】在不使用条件语句和三元运算符的情况下,在 C 中查找最多三个数【英文标题】:Find maximum of three number in C without using conditional statement and ternary operator 【发布时间】:2011-10-27 18:57:39 【问题描述】:

我必须找到用户提供的最多三个数字,但有一些限制。不允许使用任何条件语句。我尝试使用如下三元运算符。

max=(a>b?a:b)>c?(a>b?a:b):c

但它再次限制使用三元运算符。 现在我不知道该怎么做?

【问题讨论】:

这绝对属于“未来不可能帮助任何人”的类别(除非他们是不称职的教育者的学生)。 使用你拥有的工具,这种问题表明你的课程可能是在浪费时间。 @paxdiablo。这不是浪费时间。而是很好的挑战。有一些可能的方法,但受到限制,因此寻找新方法的挑战.. @Chirag,没有有用结果的挑战是无用的挑战。你会在哪一段真实世界的代码中使用这样的野兽?你会在代码审查中被涂上焦油和羽毛。约翰,如果你想教别人有关短路操作的知识,有比使用可疑谜题更好的方法。 @paxdiablo:这样的谜题对智力锻炼很有好处,尤其是当你是学生的时候。 :-) @paxdiablo: 老实说,我认为这是位操作的练习 【参考方案1】:

利用布尔表达式中的短路:

int max(int a, int b, int c)

     int m = a;
     (m < b) && (m = b); //these are not conditional statements.
     (m < c) && (m = c); //these are just boolean expressions.
     return m;

说明:

x &amp;&amp; y 等布尔值AND 运算中,当且仅当 x 为真时才会评估y。如果x 为假,则不计算y,因为整个表达式将是假的,甚至可以在不计算y 的情况下推导出来。当布尔表达式的值可以在不计算其中所有操作数的情况下推导出来时,这称为短路。

将这个原则应用到上面的代码中。最初 ma。现在如果(m &lt; b) 为真,那么这意味着b 大于m(实际上是a),所以第二个子表达式(m = b) 被求值并且m 被设置为b。然而,如果(m &lt; b) 为假,则不会计算第二个子表达式并且m 将保持a(大于b)。以类似的方式,计算第二个表达式(在下一行)。

简而言之,您可以将表达式(m &lt; x) &amp;&amp; (m = x) 解读为:将m 设置为x 当且仅当 m 小于x(m &lt; x) 是真的。希望这可以帮助您理解代码。

测试代码:

int main() 
        printf("%d\n", max(1,2,3));
        printf("%d\n", max(2,3,1));
        printf("%d\n", max(3,1,2));
        return 0;

输出:

3
3
3

注意max 的实现会给出警告,因为没有使用评估表达式:

prog.c:6:警告:未使用计算的值 prog.c:7:警告:未使用计算的值

为避免这些(无害的)警告,您可以将max 实现为:

int max(int a, int b, int c)

     int m = a;
     (void)((m < b) && (m = b)); //these are not conditional statements.
     (void)((m < c) && (m = c)); //these are just boolean expressions.
     return m;

诀窍是我们现在是casting the boolean expressions to void, which causes suppression of the warnings:

【讨论】:

能解释一下吗? 哈。这既聪明又狡猾。 @Chirag Fanse:他在利用短路。 请注意,此解决方案并未避免分支,因此虽然C++ 代码中没有条件表达式,但实际代码中存在分支。 &amp;&amp; 是一个隐含的if。如果避免 if 或三元运算符 (?:) 的目的是避免可以加速 CPU 处理的分支,那么其他解决方案会更好。 @Nawaz:避免分支,这反过来可以加速处理,因为当出现分支错误预测时,编译器必须刷新部分处理管道,这可能昂贵昂贵的相对含义)。通常,当您想避免输入if 时,您实际上并不想避免输入这两个字母,而是要避免输入 if 插入到程序中的分支。也就是说,如果你关心的话,在大多数情况下,你甚至不应该考虑这种低级别的微优化。【参考方案2】:

更新: 4 年后看这个,我发现如果两个或多个值恰好相等,它就会失败。用&gt;= 替换&gt; 会改变行为,但不能解决问题。它可能仍然可以挽救,所以我不会删除它,但不要在生产代码中使用它。


好的,这是我的:

int max3(int a, int b, int c)

    return a * (a > b & a > c) +
           b * (b > a & b > c) +
           c * (c > a & c > b);

注意使用&amp; 而不是&amp;&amp; 可以避免任何条件代码;它依赖于 &gt; 总是产生 0 或 1 的事实。(为 a &gt; b 生成的代码可能涉及条件跳转,但它们在 C 中不可见。)

【讨论】:

一个不错的,当然是恒定的时间,答案。 也许可以挽救为a * (a &gt;= b &amp; a &gt;= c) | b * (b &gt;= a &amp; b &gt;= c) | c * (c &gt;= a &amp; c &gt;= b); @chux:很有趣。它适用于我尝试过的简单示例。我得考虑一下,稍后再更新。【参考方案3】:

您可以使用此代码找出两个中最大的:

maxa,b = abs(a-b)/2 + (a+b)/2

然后再次使用它找到第三个数字:

maxa,b,c = max(a,max(b,c))

看到这适用于正数,您可以将其更改为也适用于负数。

【讨论】:

【参考方案4】:

试试这个。

#include "stdio.h"
main() 
    int a,b,c,rmvivek,arni,csc; 
    printf("enter the three numbers");
    scanf("%d%d%d",&a,&b,&c);
    printf("the biggest value is %d",(a>b&&a>c?a:b>c?b:c));

【讨论】:

我知道这是一个旧的解决方案,但 OP 明确表示没有三元运算符。【参考方案5】:

没有conditional statements,只是循环和赋值。和其他人的答案完全不同:)

while (a > b)

    while (a > c)
    
        tmp = a;
        goto finish;
    
    tmp = c;
    goto finish;

while (b > c)

    tmp = b;
    goto finish;

tmp = c;
finish: max = tmp;

【讨论】:

一个(未展开的)循环一个条件语句。【参考方案6】:
#include "stdafx.h"
#include <iostream>
int main()
       
        int x,y,z;
        scanf("%d %d %d", &x,&y, &z);
        int max = ((x+y) + abs(x-y)) /2;
        max = ((max+z) + abs(max-z)) /2;
        printf("%d ", max);
        return 0;
            

【讨论】:

【参考方案7】:
int compare(int a,int b, intc)

    return (a > b ? (a > c ? a : c) : (b > c ? b : c))

【讨论】:

这个有三个三元运算符。【参考方案8】:

布尔值运算符(包括 b)要么全为零(如果 a

int max(int a, int b)

    long d = (long)b - (long)a;
    int m = (int)(d >> 63);
    return a & m | b & ~m;


int max(int a, int b, int c)

    long d;
    int m;
    d = (long)b - (long)a;
    m = (int)(d >> 63);
    a = a & m | b & ~m;
    d = (long)c - (long)a;
    m = (int)(d >> 63);
    return a & m | c & ~m;

【讨论】:

【参考方案9】:

没有条件。只有一个转换为 uint。完美的解决方案。

int abs (a)  return (int)((unsigned int)a); 
int max (a, b)  return (a + b + abs(a - b)) / 2; 
int min (a, b)  return (a + b - abs(a - b)) / 2; 


void sort (int & a, int & b, int & c)

   int max = max(max(a,b), c);
   int min = min(min(a,b), c);
   int middle = middle = a + b + c - max - min;
   a = max;
   b = middle;
   c = min;

【讨论】:

1) sort (int &amp; a, int &amp; b, int &amp; c) 不是有效的 C 语法。 2) a-b 溢出导致 C 中的 UB,约占所有 a,b 组合的一半。 @chux 在我的脑海中,我认为这只是所有组合的四分之一。从 0(不可能溢出)到 MAX_INT(所有组合的一半导致溢出)。结果应该在中间。同样适用于 0 到 MIN_INT【参考方案10】:

只是添加另一种替代方法以避免条件执行(这不是我会使用的,但似乎从一组解决方案中丢失):

int max( int a, int b, int c ) 
   int l1[] =  a, b ;
   int l2[] =  l1[ a<b ], c ;
   return l2[ l2[0] < c ];

该方法使用(与大多数其他方法一样),布尔表达式的结果在转换为 int 时会产生 0 或 1。两个值的简化版本是:

int max( int a, int b ) 
   int lookup[]  a, b ;
   return lookup[ a < b ];

如果表达式a&lt;b 正确,我们将返回b,小心地存储在查找数组的第一个索引中。如果表达式为 false,那么我们返回 a,它被存储为查找数组的元素 0。使用它作为构建块,您可以说:

int max( int a, int b, int c ) 
   int lookup[ max(a,b), c ];
   return lookup[ max(a,b) < c ];

可以通过使用已经存储在lookup[0] 中的结果避免第二次调用内部max 并内联对max(int,int) 的原始调用,将其简单地转换为上面的代码。


(这部分只是你在得出结论之前必须衡量的另一个证据,请参阅最后的编辑)

至于我将实际使用哪个...好吧,可能是@Foo Baa here 修改为使用内联函数而不是宏。下一个选项将是这个选项或@MSN here 提供的选项。

这三种解决方案在公认答案中没有出现的共同点是,它们不仅避免了if 或三元运算符?: 的语法结构,而且还避免了分支总的来说,这可能会对性能产生影响。在没有分支的情况下,CPU 中的 branch-predictor 不可能错过。


考虑性能时,先衡量再考虑

我实际上已经为 2-way max 实现了一些不同的选项,并分析了编译器生成的代码。以下三种解决方案生成所有相同的汇编代码:

int max( int a, int b )  if ( a < b ) return b; else return a; 
int max( int a, int b )  return (a < b? b : a ); 
int max( int a, int b ) 
   (void)((a < b) && (a = b));
   return a;

这并不奇怪,因为这三个都代表完全相同的操作。有趣的信息是生成的代码不包含任何分支。使用cmovge 指令实现很简单(在intel x64 平台上使用g++ 进行测试):

movl    %edi, %eax       # move a into the return value
cmpl    %edi, %esi       # compare a and b
cmovge  %esi, %eax       # if (b>a), move b into the return value
ret

诀窍在于条件移动指令,它可以避免任何潜在的分支。

其他解决方案都没有任何分支,但它们都转换为比其中任何一个更多的 cpu 指令,这最终让我们放心,我们应该始终编写简单的代码和让编译器为我们优化吧。

【讨论】:

cmovge 是一个分支语句。它看起来是前一个条件的结果,并相应地采取行动。它恰好是一个很好的条件指令,但它仍然是一个分支。 @awoodland:我不认为你可以称它为分支,代码流不可能有两个方向。它确实考虑了产生一个或其他结果的先前值,但它不能错误预测,它不必刷新指令管道......它可能会比其他指令慢(它无法重新排序,执行取决于先前的执行,因此无法与之并行化...)但它不会分支,除非我在这里遗漏了什么。 它的行为取决于条件寄存器,我会说这是一个分支的定义。从逻辑上讲,它的行为看起来像一个分支——指令完成后,内存中的值可以成为两件事之一。如果可以预测并因此预测错误,那么我对当前的处理器设计就不太确定了。可以错误地做出“什么都不做”路径的预测并非不可想象,并且预测/错误预测无论如何都不是指令成为分支的先决条件。 (我同意它回答了这个问题——这不是一个条件陈述) @awoodland:我对什么是branch 有不同的理解,但我不是汇编专家。对我来说是代码中的一个点,其中下一条指令(注意:不是指令的结果,而是下一条指令)可以是一组中的一个(即条件跳转),相关的问题是处理器指令流水线在预测错误的情况下可能不得不放弃。在这种情况下,无论寄存器值是否改变,下一条指令总是已知的,无论值是否更新,指令流水线都能发挥最大的作用。【参考方案11】:
int fast_int_max(int a, int b)

    int select= -(a < b);
    unsigned int b_mask= select, a_mask= ~b_mask;

    return (a&a_mask)|(b&b_mask);


int fast_int_max3(int a, int b, int c)

    return fast_int_max(a, fast_int_max(b, c));

【讨论】:

【参考方案12】:

假设您正在处理整数,那么:

#define max(x,y) (x ^ ((x ^ y) & -(x < y)))
int max3(int x, int y, int z) 
    return max(max(x,y),z);

【讨论】:

【参考方案13】:
max =  a > b ? ( a > c ? a : c ) : ( b > c ? b : c ) ;

【讨论】:

@hari.I 试过这样,但同样,您使用的是不允许的三元运算符... 顺便说一句,这是一个三元,已经打折了。

以上是关于在不使用条件语句和三元运算符的情况下,在 C 中查找最多三个数的主要内容,如果未能解决你的问题,请参考以下文章

三元运算符的嵌套详解:分别在JSTLJavaScript和Java中

将三元条件运算符转换为 if 语句?

Python中的三元运算符是如何实现的

流程控制语句

[Python]_[初级]_[三元运算表达式]

[Python]_[初级]_[三元运算表达式]