((a + (b & 255)) & 255)는 ((a + b) & 255)와 같은가요?


92

일부 C ++ 코드를 탐색하고 있는데 다음과 같은 것을 발견했습니다.

(a + (b & 255)) & 255

두 배로 나를 짜증나게 했으므로 다음과 같이 생각했습니다.

(a + b) & 255

( ab32 비트 부호없는 정수)

내 이론을 확인하기 위해 테스트 스크립트 (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 제목에 대해 죄송합니다. 자유롭게 편집하십시오.


4
그 스크립트는 어떤 언어입니까? Math.random()[0,1)에서 정수 또는 double을 반환 합니까 ? 나는 당신의 대본이 (내가 말할 수있는 가장 좋은) 당신이 제기 한 문제를 전혀 반영하지 않는다고 생각합니다.
Brick

7
C / C ++ 코드 란? 그들은 다른 언어입니다.
Weather Vane

14
JS에서 테스트하려는 동작을 재현 할 수 없습니다. 그렇기 때문에 모든 사람들이 언어 선택에 대해 당신 만 있습니다. JS는 강력한 유형이 아니며 대답은 C / C ++의 변수 유형에 따라 크게 달라집니다. JS는 당신이 묻는 질문을 감안할 때 완전히 넌센스입니다.
Brick

4
@WeatherVane 자바 스크립트 함수 이름을 사용하는 본질적인 의사 코드입니다. 그의 질문의 행동에 관한 것입니다 &+C와 C ++의 부호없는 정수에.
Barmar 2016

11
"나는 테스트 프로그램을 작성했고 가능한 모든 입력에 대해 예상 한 답을 얻었습니다."는 실제로 무언가가 예상 한대로 작동한다는 보장이 없습니다. 정의되지 않은 행동은 그렇게 불쾌 할 수 있습니다. 자신의 코드가 옳다고 확신 한 후에 만 ​​예상치 못한 결과를 제공합니다.

답변:


78

그들은 동일합니다. 다음은 증거입니다.

먼저 신원 확인 (A + B) mod C = (A mod C + B mod C) mod C

하자가 관련하여 문제를 다시 언급 a & 255에 대해 서로 a % 256. a서명되지 않았기 때문에 이것은 사실 입니다.

그래서 (a + (b & 255)) & 255입니다(a + (b % 256)) % 256

이 같은입니다 (a % 256 + b % 256 % 256) % 256(제가 위에서 언급 한 정체성을 적용한 : 노트 mod%. 부호없는 형식에 대한 동일)

이것은을 단순화 (a % 256 + b % 256) % 256가되는 (a + b) % 256(정체성을 다시 적용). 그런 다음 비트 연산자를 다시 넣어

(a + b) & 255

증거를 완성합니다.


81
오버플로 가능성을 무시한 수학적 증명입니다. 고려하십시오 A=0xFFFFFFFF, B=1, C=3. 첫 번째 정체성은 유지되지 않습니다. (부호없는 산술에서는 오버플로가 문제가되지 않지만 약간 다릅니다.)
AlexD

4
실제로는와 (a + (b & 255)) & 255동일합니다 (a + (b % 256)) % N % 256. 여기서는 N최대 부호없는 값보다 하나 더 큽니다. (후자의 공식은 수학 정수의 산술로 해석됩니다.)

17
이와 같은 수학적 증명은 컴퓨터 아키텍처에서 정수의 동작을 증명하는 데 적합하지 않습니다.
Jack Aidley 2011

25
@JackAidley : 올바르게 수행되면 적절 합니다 (오버플로 고려를 소홀히했기 때문에 그렇지 않습니다).

3
@Shaz : 그것은 테스트 스크립트의 사실이지만 질문의 일부는 아닙니다.

21

부호없는 결과를 생성하기위한 부호없는 숫자의 위치 덧셈, 빼기 및 곱셈에서 입력의 유효 숫자가 더 많아도 결과의 중요도가 낮은 숫자에 영향을주지 않습니다. 이것은 십진 산술만큼이나 이진 산술에도 적용됩니다. 또한 "2의 보수"부호있는 산술에는 적용되지만 부호 크기 부호있는 산술에는 적용되지 않습니다.

그러나 우리는 이진 산술에서 규칙을 가져와 C에 적용 할 때주의해야합니다. (저는 C ++이이 작업에 대해 C와 동일한 규칙을 가지고 있지만 100 % 확실하지는 않습니다) C 산술에는 우리를 넘어 뜨릴 수있는 신비한 규칙이 있기 때문입니다. 쪽으로. C의 부호없는 산술은 간단한 이진 랩 어라운드 규칙을 따르지만 부호있는 산술 오버플로는 정의되지 않은 동작입니다. 어떤 상황에서는 C가 서명되지 않은 유형을 (서명 된) int로 자동으로 "승격"합니다.

C에서 정의되지 않은 동작은 특히 교활 할 수 있습니다. 멍청한 컴파일러 (또는 낮은 최적화 수준의 컴파일러)는 이진 산술에 대한 이해를 바탕으로 예상 한 작업을 수행 할 가능성이 있지만 최적화 컴파일러는 이상한 방식으로 코드를 손상시킬 수 있습니다.


따라서 질문의 공식으로 돌아 가면 동등성은 피연산자 유형에 따라 다릅니다.

크기가 크기보다 크거나 같은 부호없는 정수 int이면 더하기 연산자의 오버플로 동작은 단순 이진 랩 어라운드로 잘 정의됩니다. 더하기 연산 전에 한 피연산자의 상위 24 비트를 마스킹할지 여부는 결과의 하위 비트에 영향을주지 않습니다.

크기가 int다음 보다 작은 부호없는 정수이면 (signed)로 승격됩니다 int. 부호있는 정수의 오버플로는 정의되지 않은 동작이지만 적어도 모든 플랫폼에서 서로 다른 정수 유형 간의 크기 차이가 너무 커서 두 개의 승격 된 값을 한 번만 추가해도 오버플로가 발생하지 않습니다. 그래서 다시 우리는 단순한 이진 산술 인수로 돌아가서 문장이 동등하다고 간주 할 수 있습니다.

크기가 int보다 작은 부호있는 정수이면 다시 오버플로가 발생할 수 없으며 2 개 보완 구현에서 표준 이진 산술 인수를 사용하여 동등하다고 말할 수 있습니다. 부호 크기 또는 구현을 보완하는 것은 동등하지 않습니다.

OTOH 만약 ab크기가 int의 크기보다 크거나 같은 부호있는 정수 라면 2 개의 보완 구현에서도 하나의 명령문이 잘 정의되고 다른 하나는 정의되지 않은 동작이되는 경우가 있습니다.


20

기본형 : a & 255 == a % 256unsigned a.

부호가 a같이 다시 작성할 수 있습니다 m * 0x100 + b일부가 서명 m, b, 0 <= b < 0xff, 0 <= 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)

그러므로:

(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^64을 위해 2^32.
  • 8 비트 및 16 비트 부호없는 정수의 경우 추가에는 int. 이것은 int분명히 이러한 작업에서 오버플로되거나 부정적이지 않으므로 모두 유효합니다.
  • 대한 서명 경우 하나의 정수, a+b또는 a+(b&255)오버 플로우, 그것은 정의되지 않은 동작입니다. 따라서 평등은 유지 될 수 없습니다. (a+b)&255정의되지 않은 동작이 있지만 그렇지 않은 경우 (a+(b&255))&255가 있습니다.

17

네, (a + b) & 255괜찮습니다.

학교에서 덧셈을 기억하십니까? 숫자로 숫자를 추가하고 다음 숫자 열에 캐리 값을 추가합니다. 나중 (더 중요한) 숫자 열이 이미 처리 된 열에 영향을 미칠 수있는 방법은 없습니다. 이 때문에 결과에서만 숫자를 제로 아웃하거나 인수에서 첫 번째로 0을 지우면 차이가 없습니다.


위의 내용이 항상 사실은 아닙니다. C ++ 표준은이를 깨뜨리는 구현을 허용합니다.

이러한 Deathstation 9000 : - ) 33 비트를 사용하는 것 int영업 의미하는 경우, unsigned short"32 비트 부호없는 정수"로. unsigned int의미가 있다면 DS9K는 int32 비트 unsigned int와 패딩 비트가 있는 32 비트를 사용해야합니다 . (부호없는 정수는 §3.9.1 / 3에 따라 서명 된 정수와 동일한 크기를 가져야하며 패딩 비트는 §3.9.1 / 1에서 허용됩니다.) 다른 크기 및 패딩 비트 조합도 작동합니다.

내가 말할 수있는 한, 이것이 그것을 깨는 유일한 방법입니다.

  • 정수 표현은 "순수 이진"인코딩 체계 (§3.9.1 / 7 및 각주)를 사용해야하며 패딩 비트와 부호 비트를 제외한 모든 비트는 2n 값을 제공해야합니다.
  • int 승격은 int소스 유형 (§4.5 / 1)의 모든 값을 나타낼 수있는 경우에만 허용되므로 값에 int기여하는 32 비트 이상과 부호 비트가 있어야합니다.
  • int있다는 이유 다른 부가 오버플 수는 32 이상 (부호 비트 수를 계산하지 않음) 이상의 값 비트를 가질 수 없다.

2
상위 비트의 가비지가 관심있는 하위 비트의 결과에 영향을주지 않는 추가 외에도 많은 다른 작업이 있습니다. x86 asm을 사용 사례로 사용하는 2의 보수에 대한이 Q & A를 참조하세요. 어떤 상황에서도 부호없는 이진 정수.
Peter Cordes 2016

2
물론 익명으로 반대 투표를 할 수있는 모든 사람의 권리는 있지만, 배울 수있는 기회로 항상 댓글에 감사드립니다.
alain nov.

2
이것은 이해하기 가장 쉬운 대답 / 주장 인 IMO입니다. 더하기 / 빼기의 캐리 / 빌리는 10 진수와 동일하게 이진수의 하위 비트에서 상위 비트 (오른쪽에서 왼쪽)로만 전파됩니다. IDK가 왜 이것을 반대표를 던지는 지.
Peter Cordes 2011

1
@Bathsheba : CHAR_BIT는 8 일 필요가 없습니다. 그러나 C 및 C ++의 부호없는 유형은 일부 비트 폭의 일반 base2 이진 정수로 동작해야합니다. UINT_MAX가 필요하다고 생각합니다 2^N-1. (N은 CHAR_BIT의 배수가 될 필요조차 없을 수도 있지만, 표준에 따라 랩 어라운드가 2의 제곱 모듈로 발생해야한다고 확신합니다.) 이상 함을 얻을 수있는 유일한 방법은 수납 할 수있을만큼 넓 a거나 모든 경우 b에 수납 할 수있을만큼 넓지 않은 서명 된 유형 a+b.
Peter Cordes 2011

2
@Bathsheba : 예, 다행히 C-as-portable-assembly-language는 실제로 서명되지 않은 유형에 대해 작동합니다. 의도적으로 적대적인 C 구현조차도 이것을 깨뜨릴 수 없습니다. C의 진정한 휴대용 비트 해킹에 대해 끔찍한 서명 유형 뿐이며 Deathstation 9000은 실제로 코드를 손상시킬 수 있습니다.
Peter Cordes 2011

14

당신은 이미 현명한 대답을 가지고 있습니다. 부호없는 산술은 모듈로 산술이므로 결과는 그대로 유지되며 수학적으로 증명할 수 있습니다.


하지만 컴퓨터의 멋진 점은 컴퓨터가 빠르다는 것입니다. 사실, 그들은 매우 빠르기 때문에 모든 유효한 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에 따르면 : Equality hold .

또한 산술 규칙이 비트 폭에 구애받지 않고 ( 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에 따르면 : Equality hold .

글쎄, 당신은 간다 :)


1 물론, 프로그램이 실수로 정의되지 않은 동작을 트리거하는 경우 그다지 증명되지 않습니다.


1
당신은 ... 32 비트 값과는 실제로 16 비트를 사용하기 쉬운 말 : D
윌리 Mentzel

1
@WilliMentzel : 흥미로운 말입니다. 처음에는 16 비트로 작동하면 표준이 다른 비트 폭에 대해 특정 동작이 없기 때문에 32 비트, 64 비트 및 128 비트에서도 동일하게 작동한다고 말하고 싶었습니다 ... 그러나 실제로는 작동한다는 것을 기억했습니다. 비트 폭이 다음보다 작은 경우 int: 작은 정수가 먼저 변환됩니다 int(이상한 규칙). 따라서 실제로 32 비트로 데모를 수행해야합니다 (이후에는 64 비트, 128 비트 등으로 확장됩니다).
Matthieu M.

2
(4294967296-1) * (4294967296-1) 가능한 결과를 모두 평가할 수 없기 때문에 어떻게 든 줄일 수 있습니까? 내 생각에 MAX는 (4294967296-1) 그렇게한다면 당신이 말한 것처럼 우리 생애 내에서 끝나지 않을 것이라고 생각합니다 ... 그래서 결국 우리는 실험에서 평등을 보여줄 수 없습니다. 적어도 당신 같은 사람에서는 설명.
Willi Mentzel 2016

1
one 2의 보완 구현에서 이것을 테스트한다고해서 Sign-magnitude 또는 Deathstation 9000 유형 너비와의 보완으로 이식 가능하다는 것이 증명되지 않습니다. 예를 들어 좁은 unsigned 유형은 int가능한 모든 것을 나타낼 수 있는 17 비트로 승격 할 수 uint16_t있지만 어디에서 a+b오버플 로 될 수 있습니다. 이것은 다음보다 좁은 부호없는 유형의 경우에만 문제가됩니다 int. C에서는 unsigned유형이 이진 정수 여야하므로 랩 어라운드는 2의 거듭 제곱 모듈로 발생합니다.
Peter Cordes 2016

1
C가 자신의 이익을 위해 너무 이식성이 있다는 것에 동의했습니다. 그것은 것 정말 당신이 때 그 경우에, 포장 의미 대신에 정의되지 않은-행동의 의미와 연산에 서명 할 수있는 2의 보수, 서명에 대한 산술 오른쪽 시프트 및 방법을 표준화하려는 경우 좋은 원하는 포장. 그런 다음 C는 정의되지 않은 동작을 남겨 두는 것을 안전하지 않게 만드는 현대적인 최적화 컴파일러 덕분에 지뢰밭 대신 휴대용 어셈블러로 다시 한 번 유용 할 수 있습니다 (적어도 대상 플랫폼의 경우. 정의되지 않은 동작은 Deathstation 9000 구현에서만 괜찮습니다. 지적하다).
Peter Cordes 2016

4

빠른 대답은 다음과 같습니다. 두 표현 모두 동일합니다.

  • 이후 ab32 비트 부호없는 정수 결과도 오버플의 경우와 동일하다. 부호없는 산술은이를 보장합니다. 결과 부호없는 정수 유형으로 표시 할 수없는 결과는 결과 유형이 표시 할 수있는 가장 큰 값보다 하나 더 큰 수의 모듈로 축소됩니다.

긴 대답은 다음과 같습니다. 이러한 표현이 다를 수있는 알려진 플랫폼은 없지만 통합 프로모션 규칙으로 인해 표준은이를 보장하지 않습니다.

  • 의 타입의 경우 ab(부호없는 32 개 비트 정수)보다 높은 순위를 갖는 int, 연산은 부호로서 수행 2 모듈로 연산된다 (32) , 모든 값 모두 식 대한 동일한 정의 결과 산출 ab.

  • 반대로 a및 의 유형 b이보다 작은 경우 int둘 다로 승격되고 int계산이 부호있는 산술을 사용하여 수행됩니다. 여기서 오버플로는 정의되지 않은 동작을 호출합니다.

    • 경우 int결과가 완벽하게 정의되어 있으므로, 오버 플로우 수도 상기 식 중 적어도 33 비트 값을 가지며, 양 식에 대해 동일한 값을 갖는다.

    • 경우 int정확히 32 비트 값을 보유하고, 계산은 오버플 값의 예를 들면, 식 a=0xFFFFFFFFb=1두 식의 오버 플로우를 일으킬 것이다. 이를 방지하려면 ((a & 255) + (b & 255)) & 255.

  • 좋은 소식은 그러한 플랫폼이 없다는 것입니다 1 .


1 더 정확하게는 그러한 실제 플랫폼이 존재하지 않지만 이러한 동작을 나타내면서도 C 표준을 준수하도록 DS9K 를 구성 할 수 있습니다 .


3
두 번째 하위 글 머리 기호 요구 사항 (1) aint(2) int값이 32 비트 보다 작습니다 (3) a=0xFFFFFFFF. 모두 사실 일 수는 없습니다.
Barry

1
@Barry : 요구 사항을 충족하는 것으로 보이는 경우는 33 int비트로 32 개의 값 비트와 1 개의 부호 비트가 있습니다.
Ben Voigt

2

오버플로가 없다고 가정하면 동일 합니다 . 두 버전 모두 오버플로에 대해 진정으로 면역이되지는 않지만 이중 버전과 버전이 더 잘 견딥니다. 이 경우 오버플로가 문제가되는 시스템은 알지 못하지만,이 경우 작성자가이를 수행하는 것을 볼 수 있습니다.


1
지정된 OP : (a 및 b는 32 비트 부호없는 정수) . int너비가 33 비트가 아니면 오버플로의 경우 에도 결과는 동일 합니다. 부호없는 산술은이를 보장합니다. 결과 부호없는 정수 유형으로 표시 할 수없는 결과는 결과 유형이 표시 할 수있는 가장 큰 값보다 하나 더 큰 수의 모듈로 축소됩니다.
chqrlie 2011

2

예, 산술로 증명할 수 있지만 더 직관적 인 답이 있습니다.

추가 할 때 모든 비트는 자신보다 더 중요한 부분에만 영향을줍니다. 덜 중요하지 않습니다.

따라서 추가하기 전에 상위 비트에 대해 수행하는 작업은 수정 된 가장 낮은 비트보다 덜 중요한 비트 만 유지하는 한 결과를 변경하지 않습니다.


0

증거는 사소하며 독자를위한 연습용으로 남겨 둡니다.

그러나 실제로 이것을 답으로 합법화하기 위해 첫 번째 코드 줄은 b** 의 마지막 8 비트 (모두 b0 으로 설정된 상위 비트 ) a를 가져와 이것을 추가 한 다음 결과 설정의 마지막 8 비트 만 모두 더 높게 설정 한다고 말합니다. 0으로 비트.

두 번째 줄은 더 높은 비트를 모두 0으로하여 마지막 8 비트를 추가 a하고 b가져옵니다.

마지막 8 비트 만 결과에서 중요합니다. 따라서 마지막 8 비트 만 입력에서 중요합니다.

** 마지막 8 비트 = 8 LSB

또한 출력이 다음과 같을 것이라는 점에 주목하는 것도 흥미 롭습니다.

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

위와 같이 8 개의 LSB 만 중요하지만 unsigned int다른 모든 비트가 0 인 결과는 입니다. 는 a + b예상 된 결과를 생산, 오버 플로우됩니다.


아니에요. Char 수학은 int와 char가 서명 될 수 있기 때문에 발생합니다.
Antti Haapala
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.