web-dev-qa-db-ja.com

条件文と三項演算子を使用せずに、Cで最大3つの数値を見つけます

ユーザーが提供する最大3つの番号を見つける必要がありますが、いくつかの制限があります。条件ステートメントの使用は許可されていません。以下のような三項演算子を使用してみました。

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

ただし、三項演算子の使用に制限されています。今、私はこれを行う方法がわかりませんか?

30

ブール式で短絡を利用する:

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 && yなどのブールAND操作では、yが評価されるifおよびxがtrueの場合のみ。 xがfalseの場合、yは評価されません。これは、式全体がfalseになり、yを評価しなくても推測できるためです。これは、ブール式の値がその中のすべてのオペランドを評価せずに推論できる場合、短絡と呼ばれます。

この原則を上記のコードに適用します。最初はmaです。 (m < b)がtrueの場合、bm(実際はa)よりも大きいため、2番目の部分式(m = b)が評価され、mbに設定されます。ただし、(m < b)がfalseの場合、2番目の部分式は評価されず、mabより大きい)のままになります。同様に、2番目の式が評価されます(次の行)。

つまり、次のように式(m < x) && (m = x)を読み取ることができます。mxに設定しますifおよびifmxより小さい(つまり、(m < 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;
}

秘Theは、今では ブール式をvoidにキャストしているため、警告が抑制される

69
Nawaz

あなたが整数を扱っていると仮定すると、どうですか:

#define max(x,y) (x ^ ((x ^ y) & -(x < y)))
int max3(int x, int y, int z) {
    return max(max(x,y),z);
}
19
Foo Bah

条件付き実行を回避するために別の代替手段を追加するだけです(これは私が使用するものではありませんが、ソリューションのセットにはないようです)。

_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になるという事実を使用します。2つの値の簡易バージョンは次のようになります。

_int max( int a, int b ) {
   int lookup[] { a, b };
   return lookup[ a < b ];
}
_

式_a<b_が正しい場合、bを返します。これは、ルックアップ配列の最初のインデックスに慎重に格納されます。式の結果がfalseの場合、ルックアップ配列の要素_0_として保存されているaを返します。これを構成要素として使用すると、次のことが言えます。

_int max( int a, int b, int c ) {
   int lookup[ max(a,b), c ];
   return lookup[ max(a,b) < c ];
}
_

既に_lookup[0]_に保存されている結果を使用して内部maxへの2回目の呼び出しを回避し、max(int,int)への元の呼び出しをインライン化することにより、上記のコードに簡単に変換できます。


(この部分は、結論に飛び込む前に測定しなければならない別の証拠です。最後の編集を参照してください)

どちらを実際に使用するかについては...まあ、おそらく@Foo Baa here のものは、マクロではなくインライン関数を使用するように変更されています。次のオプションは、これまたは@MSN here によるオプションです。

受け入れられた答えにないこれら3つのソリューションの共通点は、ifまたは三項演算子_?:_の構文構造を回避するだけでなく、分岐全体で、パフォーマンスに影響を与える可能性があります。 CPUのbranch-predictorは、分岐がない場合に見逃すことはありません。


パフォーマンスを検討する場合、最初に測定してから考える

実際、2ウェイマックスのさまざまなオプションをいくつか実装し、コンパイラによって生成されたコードを分析しました。次の3つのソリューションは、すべて同じアセンブリコードを生成します。

_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;
}
_

3つすべてがまったく同じ操作を表しているため、これは驚くことではありません。興味深い情報は、生成されたコードにブランチが含まれていないことです。実装はcmovge命令で簡単です(インテル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命令に変換されます。これにより、1日の終わりに、単純なコードを常に書く必要がありますコンパイラに最適化させます。

PDATE: 4年後、これを見ると、2つ以上の値が等しくなるとひどく失敗することがわかります。 >>=に置き換えると動作は変わりますが、問題は修正されません。それはまだ救助可能であるかもしれないので、私はまだそれを削除しませんが、実動コードでこれを使用しないでください。


わかりました、ここに私のものがあります:

int max3(int a, int b, int c)
{
    return a * (a > b & a > c) +
           b * (b > a & b > c) +
           c * (c > a & c > b);
}

&ではなく&&を使用すると、条件コードが回避されることに注意してください。 >は常に0または1を生成するという事実に依存します(a > bに対して生成されるコードには条件付きジャンプが含まれる場合がありますが、Cからは見えません)。

6
Keith Thompson
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));
}
4
MSN

ブール値演算子(<、&&などを含む)は通常、マシンコードレベルでの条件付き演算に変換されるため、チャレンジの精神を満たしてはいけません。合理的なコンパイラが条件付きジャンプのない算術命令のみに変換されるソリューションを次に示します(longはintよりも多くのビットを持ち、そのlongは64ビットであると仮定します)。 「m」はb-aの符号ビットをキャプチャして複製するため、mはすべて1ビット(a> bの場合)またはすべてゼロのビット(a <= bの場合)です。 longはオーバーフローを避けるために使用されることに注意してください。何らかの理由でb-aがオーバーフロー/アンダーフローしないことを知っている場合、longを使用する必要はありません。

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;
}
3
gsk

条件なし。 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;
}
2
Lee Louviere

このコードを使用して、2つのうち最大のものを見つけることができます。

max{a,b} = abs(a-b)/2 + (a+b)/2

その後、もう一度使用して3番目の番号を見つけます。

max{a,b,c} = max(a,max(b,c))

これが正の数で機能することを確認し、負の数でも機能するように変更できます。

1
user5243048

条件文 ではなく、ループと割り当てのみ。そして、他の人の答えとはまったく異なる:)

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;
0
mok
#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;
}            
0
Shqear