부호없는 정수 빼기가 정의 된 동작입니까?


100

결과가 음수 일 때 동일한 유형의 다른 정수에서 부호없는 정수를 빼는 데 문제가 있다고 생각하는 사람의 코드를 보았습니다. 따라서 이와 같은 코드는 대부분의 아키텍처에서 작동하더라도 올바르지 않습니다.

unsigned int To, Tf;

To = getcounter();
while (1) {
    Tf = getcounter();
    if ((Tf-To) >= TIME_LIMIT) {
        break;
    } 
}

이것은 내가 찾을 수있는 C 표준에서 모호하게 관련된 유일한 인용문입니다.

부호없는 피연산자를 포함하는 계산은 결과로 나타나는 부호없는 정수 형식으로 나타낼 수없는 결과가 결과 형식으로 나타낼 수있는 가장 큰 값보다 하나 더 큰 숫자로 축소되기 때문에 절대로 과잉 흐름이 발생하지 않습니다.

나는 오른쪽 피연산자가 더 클 때 연산이 모듈로 잘린 숫자의 맥락에서 의미가 있도록 조정된다는 것을 의미하는 따옴표를 취할 수 있다고 가정합니다.

0x0000-0x0001 == 0x 1 0000-0x0001 == 0xFFFF

구현 종속 서명 의미를 사용하는 것과 반대 :

0x0000-0x0001 == (부호 없음) (0 + -1) == (0xFFFF뿐만 아니라 0xFFFE 또는 0x8001)

어떤 해석이 맞습니까? 그것은 전혀 정의되어 있습니까?


3
표준에서 단어를 선택하는 것은 불행합니다. "넘칠 수 없다"는 것은 오류 상황이 아님을 의미합니다. 값을 넘치게하는 대신 표준의 용어를 사용하여 "wraps".
danorton

답변:


107

부호없는 유형에서 음수를 생성하는 빼기 결과는 잘 정의되어 있습니다.

  1. [...] 부호없는 피연산자를 포함하는 계산은 결과 형식으로 나타낼 수있는 가장 큰 값보다 하나 더 큰 수만큼 부호없는 정수 형식으로 표시 할 수없는 결과가 축소되므로 절대 오버플로 할 수 없습니다. (ISO / IEC 9899 : 1999 (E) §6.2.5 / 9)

보시다시피, (unsigned)0 - (unsigned)1모듈로 UINT_MAX + 1, 즉 UINT_MAX는 -1 과 같습니다.

"부호없는 피연산자를 포함하는 계산은 절대 오버플로 할 수 없습니다."라고 표시되어있어 상한을 초과하는 경우에만 적용된다고 믿게 만들 수 있지만 이는 문장의 실제 바인딩 부분에 대한 동기 로 제시됩니다 . "a 결과로 나타나는 부호없는 정수 유형으로 표현할 수없는 결과는 결과 유형으로 표현할 수있는 가장 큰 값보다 하나 더 큰 수의 모듈로 축소됩니다. " 이 문구는 유형의 상한의 오버플로에 제한되지 않으며 표현하기에는 너무 낮은 값에도 동일하게 적용됩니다.


2
감사합니다! 나는 이제 내가 놓친 해석을 본다. 나는 그들이 더 명확한 표현을 선택할 수 있었다고 생각한다.

4
나는 제로 주변에 서명되지 않은 추가 롤 및 신체 상해 발생하는 경우 때문에, 그것은 것입니다 것을 알고, 지금은 훨씬 더 기분이 uint항상 수학적 표현하기 위해 의도 된 정수의 0통한을 UINT_MAX덧셈과 곱셈 모듈의 운영과, UINT_MAX+1그리고 때문이 아니라 오버플로. 그러나 링이 기본 데이터 유형 인 경우 언어가 다른 크기의 링에 대해 더 일반적인 지원을 제공하지 않는 이유에 대한 의문을 제기합니다.
Theodore Murdock

2
@TheodoreMurdock 그 질문에 대한 답은 간단하다고 생각합니다. 내가 말할 수있는 한, 그것이 반지라는 사실은 원인이 아니라 결과입니다. 실제 요구 사항은 서명되지 않은 유형의 모든 비트가 값 표현에 참여해야한다는 것입니다. 링과 같은 행동이 자연스럽게 흘러 나옵니다. 다른 유형에서 이러한 동작을 원한다면 필요한 계수를 적용한 다음 산술을 수행하십시오. 기본 연산자를 사용합니다.
underscore_d

@underscore_d 물론 ... 왜 그들이 디자인 결정을 내 렸는지 분명합니다. "데이터 유형이 링으로 지정 되었기 때문에 산술 오버플로가 없습니다"라고 대략적으로 사양을 작성했다는 것은 단지 재미 있습니다. 마치이 디자인 선택은 프로그래머가 오버와 언더를 조심스럽게 피할 필요가 없음을 의미하는 것처럼 -흐르거나 그들의 프로그램이 눈에 띄게 실패하게합니다.
Theodore Murdock

120

부호없는 유형으로 작업 할 때 모듈 식 산술 ( "둘러싸 기" 동작 이라고도 함 )이 발생합니다. 이 모듈 식 산술 을 이해하려면 다음 시계를 살펴보십시오.

여기에 이미지 설명 입력

9 + 4 = 1 ( 13 mod 12 )이므로 다른 방향으로는 1-4 = 9 ( -3 mod 12 )입니다. 서명되지 않은 유형으로 작업하는 동안에도 동일한 원칙이 적용됩니다. 경우] 결과 유형unsigned후 모듈러 산술 일어난다.


이제 결과를 저장하는 다음 작업을 살펴보십시오. unsigned int .

unsigned int five = 5, seven = 7;
unsigned int a = five - seven;      // a = (-2 % 2^32) = 4294967294 

int one = 1, six = 6;
unsigned int b = one - six;         // b = (-5 % 2^32) = 4294967291

결과가인지 확인하려면 변수에 signed저장 signed하거나 signed. 숫자의 차이를 확인하고 모듈 식 산술이 적용되지 않도록하려면 다음에 abs()정의 된 함수 사용을 고려해야 합니다.stdlib.h .

int c = five - seven;       // c = -2
int d = abs(five - seven);  // d =  2

특히 조건을 작성할 때 다음과 같은 이유로 매우주의하십시오.

if (abs(five - seven) < seven)  // = if (2 < 7)
    // ...

if (five - seven < -1)          // = if (-2 < -1)
    // ...

if (one - six < 1)              // = if (-5 < 1)
    // ...

if ((int)(five - seven) < 1)    // = if (-2 < 1)
    // ...

그러나

if (five - seven < 1)   // = if ((unsigned int)-2 < 1) = if (4294967294 < 1)
    // ...

if (one - six < five)   // = if ((unsigned int)-5 < 5) = if (4294967291 < 5)
    // ...

4
시계가있는 좋은 사람이지만 증거 가 이것이 정답이 될 것입니다. 질문의 전제는 이미이 모든 것이 사실 일 수 있다는 주장을 포함하고 있습니다.
궤도에서 가벼운 경주

5
@LightnessRacesinOrbit : 감사합니다. 누군가 매우 도움이 될 것 같아서 썼습니다. 나는 그것이 완전한 대답이 아니라는 데 동의합니다.
LihO 2013

4
int d = abs(five - seven);이 좋지 않습니다. 첫 번째 five - seven가 계산됩니다. promotion은 피연산자 유형을 그대로 unsigned int두고 결과는 modulo로 계산 (UINT_MAX+1)되며로 평가됩니다 UINT_MAX-1. 그러면이 값이에 대한 실제 매개 변수 abs이며 이는 나쁜 소식입니다. abs(int)인수가 범위 내에 있지 않고 abs(long long)값을 보유 할 수 있기 때문에 정의되지 않은 동작이 인수를 전달하도록 하지만 반환 값이 int초기화되도록 강제 변환되면 정의되지 않은 동작이 발생합니다 d.
Ben Voigt 2015 년

1
@LihO : 상황에 따라 달라지고 결과가 사용되는 방식에 따라 다르게 작동하는 C ++의 유일한 연산자는 사용자 지정 변환 연산자 operator T()입니다. 우리가 논의하는 두 표현식의 추가 unsigned int는 피연산자 유형을 기반으로 type 에서 수행됩니다 . 추가 결과는 unsigned int입니다. 그런 다음 그 결과는 컨텍스트에서 필요한 유형으로 암시 적으로 변환됩니다. 변환은 값이 새 유형에서 표현할 수 없기 때문에 실패합니다.
Ben Voigt 2015 년

1
@LihO : 그것은 생각하는 데 도움이 될 수 있습니다 double x = 2/3;double y = 2.0/3;
벤 보이트

5

음, 첫 번째 해석이 맞습니다. 그러나이 문맥에서 "서명 된 의미론"에 대한 귀하의 추론은 잘못되었습니다.

다시 말하지만, 첫 번째 해석이 맞습니다. 부호없는 산술은 모듈로 산술의 규칙을 따릅니다. 즉, 부호없는 32 비트 유형 0x0000 - 0x00010xFFFF대해 평가됩니다 .

그러나 동일한 결과를 생성하려면 두 번째 해석 ( "서명 된 의미"에 기반한 해석)도 필요합니다. 즉 0 - 1, 서명 된 유형의 도메인에서 평가 -1하고 중간 결과로 얻는 경우에도 나중에 서명되지 않은 유형으로 변환 될 때 -1생성해야 0xFFFF합니다. 일부 플랫폼에서 부호있는 정수 (1의 보수, 부호있는 크기)에 대해 이국적인 표현을 사용하더라도이 플랫폼은 부호있는 정수 값을 부호없는 값으로 변환 할 때 모듈로 산술 규칙을 적용해야합니다.

예를 들어,이 평가

signed int a = 0, b = 1;
unsigned int c = a - b;

플랫폼이 부호있는 정수에 대해 이국적인 표현을 사용하는 경우에도 UINT_MAX에서 생성되도록 보장됩니다 c.


4
32 비트가 아닌 16 비트 unsigned 유형을 의미한다고 생각합니다.
xioxox

4

유형의 부호 숫자 unsigned int이상, 형식 변환의 부재에 a-b첨가 할 때, 부호 번호 수득 같이 정의된다 b,을 산출 할 것이다 a. 음수를 부호없는 숫자로 변환하는 것은 부호가 반전 된 원래 숫자에 더 해지면 0이되는 숫자를 산출하는 것으로 정의됩니다 (따라서 -5를 부호없는 숫자로 변환하면 5에 더 해지면 0이되는 값이 산출됩니다). .

보다 작은 부호없는 숫자 는 빼기 전에 unsigned int입력하도록 승격 될 수 int있으며의 동작은 a-b의 크기에 따라 달라집니다 int.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.