2つのintのどちらが大きいかを本質的に確認する関数に取り組んでいます。渡されるパラメーターは2
32ビット整数です。トリックは、許可される唯一の演算子は! ~ | & << >> ^
です(キャストなし、signed int、*、/、-以外の他のデータ型など)。
これまでの私の考えは、2つのバイナリを一緒に^
して、それらが共有していない1
値のすべての位置を確認することです。次に、その値を取得して、1
を一番左に分離します。次に、どれがその価値を持っているかを確認します。その値は大きくなります。 (たとえば、32ビットではなく8ビットの整数を使用するとします)。渡された2つの値が01011011
と01101001
の場合、^
を使用して00100010
を取得しました。次に、それを00100000
、つまり01xxxxxx -> 01000000
にして、最初の数値&
を!!
にして結果を返します。 1
の場合、最初の#
が大きくなります。
どのように01xxxxxx -> 01000000
をするか、何か他に役立つことについて何か考えはありますか?
注意するのを忘れました:ifs、whiles、forsなどはありません...
次に、ループのないバージョンを示します。これは、O(lg b)演算で符号なし整数を比較します。ここで、bはマシンのワードサイズです。 OPにはsigned int
以外のデータタイプは記載されていないため、この回答の上部がOPの仕様を満たしていないようです。 (下のスポイラーバージョン。)
キャプチャしたい動作は、最上位ビットの不一致がa
の1
とb
の0
の場合です。これについての別の考え方は、a
のビットがb
の対応するビットよりも大きいということは、a
にb
の前のビットがなかった限り、a
の__bitable_name__がb
よりも大きいことを意味します。
そのために、a
の対応するビットよりも大きいb
のすべてのビットを計算し、a
の対応するビットよりも小さいb
のすべてのビットを同様に計算します。ここで、「より小さい」ビットの下にあるすべての「より大きい」ビットをマスクして、すべての「より小さい」ビットを右に塗りつぶして、マスクを作成します。最上位ビットがすべて設定されます最下位ビットまでの道は1
になりました。
あとは、単純なビットマスキングロジックを使用して、「より大きい」ビットセットを削除するだけです。
結果の値は、a <= b
の場合は0、a > b
の場合はゼロ以外です。後者の場合に1
にしたい場合は、同様の不鮮明なトリックを実行して、最下位ビットを確認します。
#include <stdio.h>
// Works for unsigned ints.
// Scroll down to the "actual algorithm" to see the interesting code.
// Utility function for displaying binary representation of an unsigned integer
void printBin(unsigned int x) {
for (int i = 31; i >= 0; i--) printf("%i", (x >> i) & 1);
printf("\n");
}
// Utility function to print out a separator
void printSep() {
for (int i = 31; i>= 0; i--) printf("-");
printf("\n");
}
int main()
{
while (1)
{
unsigned int a, b;
printf("Enter two unsigned integers separated by spaces: ");
scanf("%u %u", &a, &b);
getchar();
printBin(a);
printBin(b);
printSep();
/************ The actual algorithm starts here ************/
// These are all the bits in a that are less than their corresponding bits in b.
unsigned int ltb = ~a & b;
// These are all the bits in a that are greater than their corresponding bits in b.
unsigned int gtb = a & ~b;
ltb |= ltb >> 1;
ltb |= ltb >> 2;
ltb |= ltb >> 4;
ltb |= ltb >> 8;
ltb |= ltb >> 16;
// Nonzero if a > b
// Zero if a <= b
unsigned int isGt = gtb & ~ltb;
// If you want to make this exactly '1' when nonzero do this part:
isGt |= isGt >> 1;
isGt |= isGt >> 2;
isGt |= isGt >> 4;
isGt |= isGt >> 8;
isGt |= isGt >> 16;
isGt &= 1;
/************ The actual algorithm ends here ************/
// Print out the results.
printBin(ltb); // Debug info
printBin(gtb); // Debug info
printSep();
printBin(isGt); // The actual result
}
}
注:これは、入力のbothの上部ビットを反転させた場合も、符号付き整数で機能するはずです。 a ^= 0x80000000
。
すべての要件(25人以下のオペレーターを含む)を満たす回答が必要な場合:
int isGt(int a, int b)
{
int diff = a ^ b;
diff |= diff >> 1;
diff |= diff >> 2;
diff |= diff >> 4;
diff |= diff >> 8;
diff |= diff >> 16;
diff &= ~(diff >> 1) | 0x80000000;
diff &= (a ^ 0x80000000) & (b ^ 0x7fffffff);
return !!diff;
}
なぜそれがうまくいくのかを説明しておきます。
変換する 001xxxxx
〜00100000
、最初に実行します。
x |= x >> 4;
x |= x >> 2;
x |= x >> 1;
(これは8ビット用です。32ビットに拡張するには、シーケンスの先頭に8および16のシフトを追加します)。
これにより、00111111
(この手法は「ビットスミアリング」と呼ばれることもあります)。次に、最初の1ビットを除くすべてを切り落とします。
x ^= x >> 1;
00100000
。
あ 署名されていない 論理(&&、||)と比較(!=、==)を使用できる場合のバリアント。
int u_isgt(unsigned int a, unsigned int b)
{
return a != b && ( /* If a == b then a !> b and a !< b. */
b == 0 || /* Else if b == 0 a has to be > b (as a != 0). */
(a / b) /* Else divide; integer division always truncate */
); /* towards zero. Giving 0 if a < b. */
}
!=
および==
は簡単に削除できます。つまり、
int u_isgt(unsigned int a, unsigned int b)
{
return a ^ b && (
!(b ^ 0) ||
(a / b)
);
}
ために 署名した 次に、次のようなものに拡張できます。
int isgt(int a, int b)
{
return
(a != b) &&
(
(!(0x80000000 & a) && 0x80000000 & b) || /* if a >= 0 && b < 0 */
(!(0x80000000 & a) && b == 0) ||
/* Two more lines, can add them if you like, but as it is homework
* I'll leave it up to you to decide.
* Hint: check on "both negative" and "both not negative". */
)
;
}
よりコンパクトにすることができます/操作を排除します。 (少なくとも1つ)ただし、わかりやすくするためにこのように記述します。
の代わりに 0x80000000
つまり、ie:
#include <limits.h>
static const int INT_NEG = (1 << ((sizeof(int) * CHAR_BIT) - 1));
これを使用してテストする:
void test_isgt(int a, int b)
{
fprintf(stdout,
"%11d > %11d = %d : %d %s\n",
a, b,
isgt(a, b), (a > b),
isgt(a, b) != (a>b) ? "BAD!" : "OK!");
}
結果:
33 > 0 = 1 : 1 OK!
-33 > 0 = 0 : 0 OK!
0 > 33 = 0 : 0 OK!
0 > -33 = 1 : 1 OK!
0 > 0 = 0 : 0 OK!
33 > 33 = 0 : 0 OK!
-33 > -33 = 0 : 0 OK!
-5 > -33 = 1 : 1 OK!
-33 > -5 = 0 : 0 OK!
-2147483647 > 2147483647 = 0 : 0 OK!
2147483647 > -2147483647 = 1 : 1 OK!
2147483647 > 2147483647 = 0 : 0 OK!
2147483647 > 0 = 1 : 1 OK!
0 > 2147483647 = 0 : 0 OK!
完全にbranchlessバージョンの Kaganarの小さいisGt関数 は次のようになります。
int isGt(int a, int b)
{
int diff = a ^ b;
diff |= diff >> 1;
diff |= diff >> 2;
diff |= diff >> 4;
diff |= diff >> 8;
diff |= diff >> 16;
//1+ on GT, 0 otherwise.
diff &= ~(diff >> 1) | 0x80000000;
diff &= (a ^ 0x80000000) & (b ^ 0x7fffffff);
//flatten back to range of 0 or 1.
diff |= diff >> 1;
diff |= diff >> 2;
diff |= diff >> 4;
diff |= diff >> 8;
diff |= diff >> 16;
diff &= 1;
return diff;
}
これは、実際の計算(x86 Arch上のMSVC 2010コンパイラー)の約60の命令に加えて、関数のプロローグ/エピローグ用に追加の10スタック演算などを追加します。
他の誰かの宿題をやりたくないので、これは我慢できませんでした。:)他の人がもっとコンパクトなものを考えることができると思います。 。
編集:しかし、いくつかのバグがあります。私はそれをOPに任せて見つけ、修正します。
#include<unistd.h>
#include<stdio.h>
int a, b, i, ma, mb, a_neg, b_neg, stop;
int flipnum(int *num, int *is_neg) {
*num = ~(*num) + 1;
*is_neg = 1;
return 0;
}
int print_num1() {
return ((a_neg && printf("bigger number %d\n", mb)) ||
printf("bigger number %d\n", ma));
}
int print_num2() {
return ((b_neg && printf("bigger number %d\n", ma)) ||
printf("bigger number %d\n", mb));
}
int check_num1(int j) {
return ((a & j) && print_num1());
}
int check_num2(int j) {
return ((b & j) && print_num2());
}
int recursive_check (int j) {
((a & j) ^ (b & j)) && (check_num1(j) || check_num2(j)) && (stop = 1, j = 0);
return(!stop && (j = j >> 1) && recursive_check(j));
}
int main() {
int j;
scanf("%d%d", &a, &b);
ma = a; mb = b;
i = (sizeof (int) * 8) - 1;
j = 1 << i;
((a & j) && flipnum(&a, &a_neg));
((b & j) && flipnum(&b, &b_neg));
j = 1 << (i - 1);
recursive_check(j);
(!stop && printf("numbers are same..\n"));
}
私は3つの操作で解決策を持っていると思います:
最初の数値に1を加算し、表現できる最大の数値(すべて1)から減算します。その番号を2番目の番号に追加します。オーバーフローする場合、最初の数値は2番目の数値よりも小さくなります。
これが正しいかどうかは100%わかりません。つまり、1を追加する必要がない可能性があります。オーバーフローをチェックできるかどうかはわかりません(そうでない場合は、最後のビットを予約して、最後に1かどうかをテストします)。
さて、コードにいくつかの問題がありましたが、私はそれと次の作品を改訂しました。
この補助関数は、数値のn番目の有効数字を比較します。
int compare ( int a, int b, int n )
{
int digit = (0x1 << n-1);
if ( (a & digit) && (b & digit) )
return 0; //the digit is the same
if ( (a & digit) && !(b & digit) )
return 1; //a is greater than b
if ( !(a & digit) && (b & digit) )
return -1; //b is greater than a
}
以下は、より大きな数を再帰的に返します。
int larger ( int a, int b )
{
for ( int i = 8*sizeof(a) - 1 ; i >= 0 ; i-- )
{
if ( int k = compare ( a, b, i ) )
{
return (k == 1) ? a : b;
}
}
return 0; //equal
}