web-dev-qa-db-ja.com

((a +(b&255))と255)は((a + b)&255)と同じですか?

私はいくつかのC++コードを閲覧していて、次のようなものを見つけました。

(a + (b & 255)) & 255

ダブルANDは私を悩ませたので、私は考えました:

(a + b) & 255

aおよびbは32ビット符号なし整数です)

理論を確認するために、テストスクリプト(JS)をすばやく作成しました。

for (var i = 0; i < 100; i++) {
    var a = Math.ceil(Math.random() * 0xFFFF),
        b = Math.ceil(Math.random() * 0xFFFF);

    var expr1 = (a + (b & 255)) & 255,
        expr2 = (a + b) & 255;

    if (expr1 != expr2) {
        console.log("Numbers " + a + " and " + b + " mismatch!");
        break;
    }
}

スクリプトは私の仮説を確認しましたが(両方の操作が等しい)、1) ランダム および2)私は数学者ではないので、私はまだそれを信用していません 私は知らない私は何をしていますか

また、LISP-yタイトルについても申し訳ありません。自由に編集してください。

92
Martin

それらは同じです。ここに証拠があります:

最初にアイデンティティ(A + B) mod C = (A mod C + B mod C) mod Cに注意してください

a & 255a % 256の代わりと見なして、問題をもう一度説明しましょう。 aは符号なしなので、これは事実です。

(a + (b & 255)) & 255(a + (b % 256)) % 256です

これは(a % 256 + b % 256 % 256) % 256と同じです(上記のIDを適用しました:mod%は符号なしの型では同等です。)

これは(a % 256 + b % 256) % 256に簡略化され、(a + b) % 256(IDの再適用)になります。次に、ビットごとの演算子を元に戻して、

(a + b) & 255

証明を完了します。

77
Bathsheba

符号なしの結果を生成するための符号なしの数値の位置の加算、減算、および乗算では、入力の有効桁数が結果の下位桁に影響しません。これは、10進数演算と同様に、2進演算にも適用されます。また、「2の補数」の符号付き算術にも適用されますが、符号の大きさの符号付き算術には適用されません。

しかし、バイナリ演算からルールを取得してCに適用するときは注意する必要があります(C++にはこのようなものと同じルールがありますが、100%確信はありません)アップ。 Cの符号なし算術は、単純なバイナリラップアラウンドルールに従いますが、符号付き算術オーバーフローは未定義の動作です。状況によってはさらに悪いことに、Cは自動的にunsigned型を(signed)intに「昇格」させます。

Cでの未定義の動作は、特に油断できません。ダムコンパイラ(または低最適化レベルのコンパイラ)は、バイナリ算術の理解に基づいて期待どおりの動作をする可能性がありますが、最適化コンパイラは奇妙な方法でコードを破壊する可能性があります。


したがって、問題の式に戻ると、同等性はオペランドのタイプに依存します。

サイズがintのサイズ以上の符号なし整数である場合、加算演算子のオーバーフロー動作は単純なバイナリラップアラウンドとして明確に定義されています。加算演算の前に1つのオペランドの上位24ビットをマスクするかどうかは、結果の下位ビットに影響しません。

サイズがintより小さい符号なし整数の場合、それらは(符号付き)intに昇格されます。符号付き整数のオーバーフローは未定義の動作ですが、少なくとも、異なる整数型間のサイズの違いに遭遇したすべてのプラットフォームでは、2つの昇格された値を1回追加してもオーバーフローは発生しません。したがって、単純なバイナリ算術引数にフォールバックして、ステートメントを同等とみなすことができます。

サイズがintより小さい符号付き整数の場合、再びオーバーフローは発生せず、2の補数の実装では、標準のバイナリ算術引数に依存して同等であると判断できます。符号の大きさや実装を補完するものでは、同等ではありません。

OTOH abがintのサイズ以上のサイズの符号付き整数である場合、2の補数実装でも、一方のステートメントが適切に定義されている場合があります。未定義の動作になります。

21
plugwash

補題:符号なしaの場合はa & 255 == a % 256

符号なしaは、m * 0x100 + bとして書き換えることができます。いくつかの符号なしmb0 <= b < 0xff0 <= m <= 0xffffff。両方の定義からa & 255 == b == a % 256になります。

さらに、次のものが必要です。

  • 分配プロパティ:(a + b) mod n = [(a mod n) + (b mod n)] mod n
  • 符号なし加算の定義、数学的に:(a + b) ==> (a + b) % (2 ^ 32)

副<文>この[前述の事実の]結果として、それ故に、従って、だから◆【同】consequently; therefore <文>このような方法で、このようにして、こんなふうに、上に述べたように◆【同】in this manner <文>そのような程度まで<文> AひいてはB◆【用法】A and thus B <文>例えば◆【同】for example; as an example:

(a + (b & 255)) & 255 = ((a + (b & 255)) % (2^32)) & 255      // def'n of addition
                      = ((a + (b % 256)) % (2^32)) % 256      // lemma
                      = (a + (b % 256)) % 256                 // because 256 divides (2^32)
                      = ((a % 256) + (b % 256 % 256)) % 256   // Distributive
                      = ((a % 256) + (b % 256)) % 256         // a mod n mod n = a mod n
                      = (a + b) % 256                         // Distributive again
                      = (a + b) & 255                         // lemma

はい、そうです。 32ビットの符号なし整数の場合。


他の整数型はどうですか?

  • 64ビット符号なし整数の場合、上記のすべても同様に適用され、2^642^32に置き換えます。
  • 8ビットおよび16ビットの符号なし整数の場合、追加にはintへの昇格が含まれます。このintは、これらの操作のいずれにおいても確実にオーバーフローしたり負になったりしないため、すべて有効のままです。
  • signed integersの場合、a+bまたはa+(b&255)オーバーフローの場合、未定義の動作です。そのため、平等は成り立ちません— (a+b)&255は未定義の動作ですが、(a+(b&255))&255はそうではない場合があります。
20
Barry

はい、(a + b) & 255で問題ありません。

学校での追加を覚えていますか?数字を1桁ずつ追加し、桁の次の列に桁上げ値を追加します。後の(より重要な)桁の列が、すでに処理された列に影響を与える方法はありません。このため、結果の桁のみ、または最初に引数の桁をゼロに設定しても違いはありません。


上記は常に正しいとは限りません。C++標準では、これを破る実装が許可されています。

そのようなDeathstation 9000 - は、OPが「32ビット符号なし整数を含むunsigned shortを意味する場合、33ビットintを使用する必要があります。 「。 unsigned intが意図されている場合、DS9Kは32ビットintと、パディングビット付きの32ビットunsigned intを使用する必要があります。 (符号なし整数は、§3.9.1/ 3に従って符号付きの整数と同じサイズにする必要があり、§3.9.1/ 1ではパディングビットが許可されています。)サイズとパディングビットの他の組み合わせも機能します。

私が知る限り、これはそれを破る唯一の方法です。

  • 整数表現は、「純粋なバイナリ」エンコーディングスキーム(§3.9.1/ 7および脚注)を使用する必要があります。パディングビットと符号ビットを除くすべてのビットは、値2を提供する必要がありますn
  • intプロモーションは、intがソース型のすべての値を表すことができる場合にのみ許可されます(§4.5/ 1)。したがって、intには、値に寄与する少なくとも32ビットと符号ビットが必要です。
  • intには32を超える値ビット(符号ビットをカウントしない)を含めることはできません。これは、加算がオーバーフローしないためです。
17
alain

あなたはすでに賢明な答えを持っています:符号なし算術はモジュロ算術であり、結果が保持されます、あなたは数学的にそれを証明することができます...


ただし、コンピューターの優れた点の1つは、コンピューターが高速であることです。実際、これらは非常に高速であるため、妥当な時間内に32ビットのすべての有効な組み合わせを列挙することが可能です(64ビットで試行しないでください)。

それで、あなたの場合、私は個人的にそれをコンピューターに投げるのが好きです。数学的な証明が正しいことおよびが監督していないことよりも、プログラムが正しいことを確信するのに時間がかかりません。仕様の詳細1

#include <iostream>
#include <limits>

int main() {
    std::uint64_t const MAX = std::uint64_t(1) << 32;
    for (std::uint64_t i = 0; i < MAX; ++i) {
        for (std::uint64_t j = 0; j < MAX; ++j) {
            std::uint32_t const a = static_cast<std::uint32_t>(i);
            std::uint32_t const b = static_cast<std::uint32_t>(j);

            auto const champion = (a + (b & 255)) & 255;
            auto const challenger = (a + b) & 255;

            if (champion == challenger) { continue; }

            std::cout << "a: " << a << ", b: " << b << ", champion: " << champion << ", challenger: " << challenger << "\n";
            return 1;
        }
    }

    std::cout << "Equality holds\n";
    return 0;
}

これは、32ビット空間でaおよびbのすべての可能な値を列挙し、等式が成り立つかどうかをチェックします。動作しない場合は、動作しなかったケースを出力します。これは、健全性チェックとして使用できます。

そして、 Clangによる平等が保持されます

さらに、算術規則がビット幅に依存しない(intビット幅を超える)場合、この同等性は、64ビットと128ビットを含む32ビット以上の符号なし整数型に適用されます。

注:コンパイラは、合理的な時間枠ですべての64ビットパターンを列挙するにはどうすればよいですか?できない。ループは最適化されました。そうしないと、実行が終了する前に全員が死亡してしまいます。


最初に証明したのは、16ビットの符号なし整数のみです。残念なことに、C++は小さな整数(intよりも小さいビット幅)が最初にintに変換される非常識な言語です。

#include <iostream>

int main() {
    unsigned const MAX = 65536;
    for (unsigned i = 0; i < MAX; ++i) {
        for (unsigned j = 0; j < MAX; ++j) {
            std::uint16_t const a = static_cast<std::uint16_t>(i);
            std::uint16_t const b = static_cast<std::uint16_t>(j);

            auto const champion = (a + (b & 255)) & 255;
            auto const challenger = (a + b) & 255;

            if (champion == challenger) { continue; }

            std::cout << "a: " << a << ", b: " << b << ", champion: "
                      << champion << ", challenger: " << challenger << "\n";
            return 1;
        }
    }

    std::cout << "Equality holds\n";
    return 0;
}

そして、もう一度、 Clangによる平等が成り立つ

まあ、そこに行きます:)


1 もちろん、プログラムが不注意で未定義の動作を引き起こした場合、それはあまり証明されません。

14
Matthieu M.

簡単な答えは次のとおりです。両方の式は同等です

  • abは32ビット符号なし整数であるため、オーバーフローの場合でも結果は同じです。符号なし算術はこれを保証します:結果の符号なし整数型で表現できない結果は、結果の型で表現できる最大値よりも1大きい数を法として減少します。

長い答えは次のとおりです。これらの表現が異なる既知のプラットフォームはありませんが、統合プロモーションのルールのために、標準はそれを保証しません。

  • aおよびb(符号なし32ビット整数)の型のランクがintよりも高い場合、計算は符号なし、2を法として実行されます。32、およびaおよびbのすべての値の両方の式に対して同じ定義済みの結果を生成します。

  • 逆に、abの型がintよりも小さい場合、両方ともintに昇格され、計算は符号付き算術を使用して実行されます。未定義の動作を呼び出します。

    • intに少なくとも33の値ビットがある場合、上記の式はどちらもオーバーフローしないため、結果は完全に定義され、両方の式で同じ値になります。

    • intの値が正確に32ビットである場合、canboth(---)式の計算canオーバーフロー、たとえば値a=0xFFFFFFFFおよびb=1は、両方の式でオーバーフローを引き起こします。これを回避するには、((a & 255) + (b & 255)) & 255と記述する必要があります。

  • 幸いなことに、そのようなプラットフォームはありません1


1 より正確には、そのような実際のプラットフォームは存在しませんが、 DS9K を設定して、そのような動作を示し、C標準に準拠することができます。

4
chqrlie

同一オーバーフローがないと仮定。どちらのバージョンもオーバーフローの影響をまったく受けませんが、ダブルとバージョンはオーバーフローに対してより耐性があります。私は、この場合のオーバーフローが問題になるシステムを認識していませんが、作成者がこれを実行している場合、これを行うのを見ることができます。

2
Loren Pechtel

はい、算術で証明できますが、より直感的な答えがあります。

追加するとき、すべてのビットはそれ自体よりも重要なものにのみ影響します。それほど重要ではありません。

したがって、追加する前に上位ビットに何をしても、変更された最下位ビットよりも重要でないビットのみを保持する限り、結果は変わりません。

2
Francesco Dondi

証拠は簡単で、読者のための演習として残されています

しかし、実際にこれを答えとして正当化するために、コードの最初の行は、b **の最後の8ビット(bのすべての上位ビットをゼロに設定)を取得し、これをaそして、結果の最後の8ビットのみを取得し、すべての上位ビットをゼロに設定します。

2行目はabを追加し、最後の8ビットを取得し、上位ビットをすべてゼロと言います。

結果では、最後の8ビットのみが重要です。したがって、入力では最後の8ビットのみが重要です。

** 最後の8ビット = 8 LSB

また、出力は次と同等になることに注意してください

char a = something;
char b = something;
return (unsigned int)(a + b);

上記のように、8 LSBのみが重要ですが、結果はunsigned int他のすべてのビットはゼロ。 a + bはオーバーフローし、期待される結果を生成します。

0
user3728501