3つの真偽値のうち2つ以上が真であるかを調べる

元ネタ
Check if at least 2 out of 3 booleans is true
http://stackoverflow.com/questions/3076078/check-if-at-least-2-out-of-3-booleans-is-true/

一番単純な例

変数2つが真であるかを検査すれば良く,順列を考えない2変数の組み合わせの数は_3C_2 = 3となり,3通りの組み合わせについて全て検査すれば良い事になる.

つまり,以下のようになる.

int
at_least2(int a, int b, int c)
{
  return ((a && b) || (a && c) || (b & c));
}

これのアセンブリコードは以下の通り.なおコンパイラMacOS X付属のgcc 4.2.1となる.

        .text
.globl _at_least2
_at_least2:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        cmpl    $0, 8(%ebp)   # a == 0 なら
        je      L2            #     L2 へジャンプ
        cmpl    $0, 12(%ebp)  # b != 0 なら
        jne     L4            #     L4 へジャンプ
L2:
        cmpl    $0, 8(%ebp)   # a == 0 なら
        je      L5            #     L2 へジャンプ
        cmpl    $0, 16(%ebp)  # c != 0 なら
        jne     L4            #     L4 へジャンプ
L5:
        cmpl    $0, 12(%ebp)  # b == 0 なら
        je      L7            #     L7 へジャンプ
        cmpl    $0, 16(%ebp)  # c == 0 なら
        je      L7            #     L7 へジャンプ
L4:
        movl    $1, -12(%ebp)
        jmp     L9
L7:
        movl    $0, -12(%ebp)
L9:
        movl    -12(%ebp), %eax
        leave
        ret
        .subsections_via_symbols

判定までに必要な最大のインストラクション数は12個(#でコメントのある行)となっている.ただし,a と b が真の場合は,一気に L4 までジャンプするので,最短では4インストラクションで結果がわかることになる.

別の解答例

もっと効率的にすると,以下のようになるとの答えがあった.

int
at_least2(int a, int b, int c)
{
  return a ? (b || c) : (b && c);
}

このコードをアセンブリにしてみると以下のようになる.

        .text
.globl _at_least2
_at_least2:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        cmpl    $0, 8(%ebp)    # a == 0 なら
        je      L2             #     L2 へジャンプ
        cmpl    $0, 12(%ebp)   # b != 0 なら
        jne     L4             #     L4 へジャンプ
        cmpl    $0, 16(%ebp)   # c == 0 なら
        je      L6             #     L6 へジャンプ
L4:
        movl    $1, -16(%ebp)
        jmp     L7
L6:
        movl    $0, -16(%ebp)
L7:
        movl    -16(%ebp), %eax
        movl    %eax, -20(%ebp)
        jmp     L8
L2:
        cmpl    $0, 12(%ebp)   # b == 0 なら
        je      L9             #     L9 へジャンプ
        cmpl    $0, 16(%ebp)   # c == 0 なら
        je      L9             #     L9 へジャンプ
        movl    $1, -12(%ebp) 
        jmp     L12
L9:
        movl    $0, -12(%ebp)
L12:
        movl    -12(%ebp), %eax
        movl    %eax, -20(%ebp)
L8:
        movl    -20(%ebp), %eax
        leave
        ret
        .subsections_via_symbols

ここでは,判定までに必要な最大のインストラクション数は10個となり,先の例よりも2インストラクション減少している.ただし,判定に最小限必要なインストラクション数は4であり,これは変わっていない.

もう少し減ってるんじゃないかと思ったけれど,2インストラクションしか減少していなかった.まぁ,割合にすると17%のパフォーマンス向上なので多いのかな?