2つの整数をどのように比較する必要がありますか?


8

私は最近、配列をソートするプログラムを書きました。そのために、私はそれに渡す比較関数を書く必要がありました。私の比較関数は1(if x> y)、-1(if x <y)、または0(if x = y)を返しているはずです。条件式を使用して通常の関数(関数1)を記述しましたが、別の方法(関数2)で記述するようにアドバイスされました。そのように書く方が良いですか?ブール条件は常に真実に対して1を返しますか?(つまり、x = 0およびy = 0の場合、常に(x == y)== 1になりますか?)

機能1:

int Icmp(void* x, void* y)
{
    int a = *(int*)x;
    int b = *(int*)y;
    if (a > b)
        return 1;
    else if (a < b)
        return -1;
    else
        return 0;
}

機能2:

int Icmp(void* x, void* y)
{
    return (*(int*)x > * (int*)y) - (*(int*)x < *(int*)y);
}

5
2番目の名目上の利点は、条件付きジャンプを回避し、パフォーマンスが向上する可能性があることです。使用されている表記は恐ろしいものです。最初のように変数をローカライズしてから、計算で使用する必要があります。
ジョナサンレフラー、

4
2番目の方法は、愚かなコンピューターでブランチを強制的に使わないようにする一般的なトリックです。最初の読みやすさを組み合わせて、両方の世界の最良のものを取得します。編集:ジョナサンが上で言ったように
アンティハパラ

8
どちらの方法でも機能します。パフォーマンスの違いを注意深く測定します。おそらく検出するのは難しいでしょう。違いがわずかである場合は、明快さを優先します。
ジョナサンレフラー、

1
理由を教えてくれた人に聞いてください。上記の利点が発生する状況を尋ねます。それらのシチュエーションがあなたにとって目立つと予想される理由を尋ねてください。これらすべての質問に対して満足のいく答えが得られない場合は、別のアドバイザーを見つけてください。可能であれば、上記の利点を測定することを検討してください。自分が認識している利点がどれほど重要であるかを考えてください。最も重要なのは、「コードを理解している」と「すべての仲間がコードをよりよく理解していると思います」です。その後、あなた自身を決定します。
ユンノシュ

2
@JoëlHecht1でない場合、Cコンパイラではありません。C以外のコンパイラを使用してCコードをコンパイルする理由は何ですか?
Antti Haapala

回答:


14

非分岐コードを記述する好ましい方法は、オペランドにローカル変数を使用することです。

int icmp(const void *x, const void *y)
{
    int a = *(const int *)x;
    int b = *(const int *)y;
    return (a > b) - (a < b);
}

式は比較関数の一般的なイディオムであり、インプレースポインター逆参照の代わりに変数を使用して記述されている場合は、かなり読みやすくなっています。

コードは><またはを使用した比較の結果が==タイプでintあり、1または0であるという事実に依存しています。これは、C標準で必要とされています。42または-1などの値を生成するコンパイラは、定義上、Cコンパイラではありません。 。

その最大を見るのは簡単です。いずれかa > bまたはa < b所与の時点で真であること、及び結果のいずれかであることができ1 - 00 - 1または0 - 0

ブランチレスコードの理由について-コンパイラーは両方の関数に対してまったく同じコードを生成する可能性がありますが、多くの場合は生成されません。たとえば、最新のGCCICCはどちらもx86-64で最初の関数の分岐を生成するようですが、後者の場合は条件付き実行の分岐なしのコードです。そして、ブランチは重要ではないと言う人には、スタックオーバーフローでこれまでに投票された最高品質のQAを紹介します。


これが正しい方法です。それは非常に読みやすく、コンパイルされたマシンコードは同一であり、追加の算術演算を優先して分岐を回避します。エントロピーの高い状態にある巨大な配列をソートする場合、分岐予測子は多くのパフォーマンスの問題を引き起こす可能性があります。これはその問題を回避します。
-3ch0

5
- ifステートメントがないことは、ブランチが生成されないという意味ではなく、- ステートメントがある場合にブランチがあるという意味でもありませんif。実際、コンパイラーがそれらを最適化できるかどうかによって異なります。
G.スリーペン、

@ G.Sliepen実際にはICCはそれらを完全に最適化することができますが、ブランチを使用して記述されているため、ブランチはおそらく実行されないという結論に達しました...
Antti Haapala

3

そのように書く方が良いですか?

私はノーだと思います。

パフォーマンスのために; それは問題ではない(最新のコンパイラーの可能性が高い)、または別個の関数であってはならない(そしてソートに使用されるコードに組み込む必要がある)か、まったくソートしない(例:作成時にソートされたデータ)作成後にソートされません)。

読みやすさ(コードのメンテナンス、元のバージョンでエラーが発生する可能性、後でエラーが発生するリスク)のために、元のバージョンが望ましいです。特にチームで作業しているとき、特に他のチームメンバーがCとは非常に異なるルールを持つ他の10のプログラミング言語に精通している場合は特にそうです。

具体的には; 私はこれが好きです(実際のコードでキャストすると物事が読みにくくなるため):

    int a = *(int*)x;
    int b = *(int*)y;

..残りを次のように書き直します。

    if (a > b) {
        return 1;
    }
    if (a < b) {
        return -1;
    }
    return 0;
}

..または次のようになります:

    if (a > b) return 1;
    if (a < b) return -1;
    return 0;
}

..のelse後は不要なのでreturn、そして、「中括弧がなく、その後に独自の行にステートメントが続く場合」は、誰かが誤って新しい行を挿入してそれを認識せずにすべてを壊すリスクを生み出すためです(例については、https://dwheeler.com/essays/apple-goto-を参照してください) fail.html)。


0

で比較機能を使用している場合 qsort、関数は+ ve、-ve、またはゼロの値を返すだけで済みます。

その場合は、数字を引くだけです

int Icmp(const void* x, const void* y)
{
    return (*(int*)x - *(int*)y);
}

これは確かにより効率的です(a > b) - (a < b)が、問題a - bは減算がアンダーフローする可能性があることです。したがって、これは悪い表現です。
chmike

-2

整数

echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1

フロート

echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1

文字列

echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1

質問はCで整数を比較する方法についてです。あなたの答えは明らかに有効なCコードではないため、質問した人に有用な情報を提供していません。回答がトピックに沿っていることを確認してください。
G.スリーペン、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.